// TODO these files need to refactored. import nconf from 'nconf'; import _ from 'lodash'; import moment from 'moment'; import { model as User } from '../../models/user'; // eslint-disable-line import/no-cycle import * as Tasks from '../../models/task'; // eslint-disable-line import/no-cycle import { // eslint-disable-line import/no-cycle model as Group, basicFields as basicGroupFields, } from '../../models/group'; import { // eslint-disable-line import/no-cycle getUserInfo, sendTxn as txnEmail, } from '../email'; import { paymentConstants } from './constants'; import { cancelSubscription, createSubscription } from './subscriptions'; // eslint-disable-line import/no-cycle const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL'); const JOINED_GROUP_PLAN = 'joined group plan'; function _dateDiff (earlyDate, lateDate) { if (!earlyDate || !lateDate || moment(lateDate).isBefore(earlyDate)) return 0; return moment(lateDate).diff(earlyDate, 'months', true); } /** * Add a subscription to members of a group * * @param group The Group Model that is subscribed to a group plan * * @return undefined */ async function addSubscriptionToGroupUsers (group) { let members; if (group.type === 'guild') { members = await User.find({ guilds: group._id }).select('_id purchased items auth profile.name notifications').exec(); } else { members = await User.find({ 'party._id': group._id }).select('_id purchased items auth profile.name notifications').exec(); } // eslint-disable-next-line no-use-before-define const promises = members.map(member => addSubToGroupUser(member, group)); await Promise.all(promises); } /** * Add a subscription to a new member of a group * * @param member The new member of the group * * @return undefined */ async function addSubToGroupUser (member, group) { // These EMAIL_TEMPLATE constants are used to pass strings into templates that are // stored externally and so their values must not be changed. const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS = 'iOS_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GROUP_PLAN = 'group_plan_free_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_LIFETIME_FREE = 'lifetime_free_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL = 'normal_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_UNKNOWN = 'unknown_type_of_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE = 'no_subscription'; // When changing customerIdsToIgnore or paymentMethodsToIgnore, the code blocks below for // the `group-member-join` email template will probably need to be changed. const customerIdsToIgnore = [ paymentConstants.GROUP_PLAN_CUSTOMER_ID, paymentConstants.UNLIMITED_CUSTOMER_ID, ]; const paymentMethodsToIgnore = [ paymentConstants.GOOGLE_PAYMENT_METHOD, paymentConstants.IOS_PAYMENT_METHOD, ]; let previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE; const leader = await User.findById(group.leader).exec(); const data = { user: {}, sub: { key: 'group_plan_auto', }, customerId: 'group-plan', paymentMethod: 'Group Plan', headers: {}, }; let plan = { planId: 'group_plan_auto', customerId: 'group-plan', dateUpdated: new Date(), gemsBought: 0, paymentMethod: 'groupPlan', extraMonths: 0, dateTerminated: null, lastBillingDate: null, dateCreated: member.purchased.plan.dateCreated || new Date(), mysteryItems: [], consecutive: { trinkets: 0, offset: 0, gemCapExtra: 0, }, }; const memberPlan = member.purchased.plan; if (member.isSubscribed()) { const customerHasCancelledGroupPlan = ( memberPlan.customerId === paymentConstants.GROUP_PLAN_CUSTOMER_ID && !member.hasNotCancelled() ); const ignorePaymentPlan = paymentMethodsToIgnore.indexOf(memberPlan.paymentMethod) !== -1; const ignoreCustomerId = customerIdsToIgnore.indexOf(memberPlan.customerId) !== -1; if (ignorePaymentPlan) { txnEmail({ email: TECH_ASSISTANCE_EMAIL }, 'admin-user-subscription-details', [ { name: 'PROFILE_NAME', content: member.profile.name }, { name: 'UUID', content: member._id }, { name: 'EMAIL', content: getUserInfo(member, ['email']).email }, { name: 'PAYMENT_METHOD', content: memberPlan.paymentMethod }, { name: 'PURCHASED_PLAN', content: JSON.stringify(memberPlan) }, { name: 'ACTION_NEEDED', content: 'User has joined group plan and has been told to cancel their subscription then email us. Ensure they do that then give them free sub.' }, // TODO User won't get email instructions if they've opted out of all emails. // See if we can make this email an exception and if not, // report here whether they've opted out. ]); } if ((ignorePaymentPlan || ignoreCustomerId) && !customerHasCancelledGroupPlan) { // member has been added to group plan but their subscription will not be changed // automatically so they need a special message in the email if (memberPlan.paymentMethod === paymentConstants.GOOGLE_PAYMENT_METHOD) { previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE; } else if (memberPlan.paymentMethod === paymentConstants.IOS_PAYMENT_METHOD) { previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS; } else if (memberPlan.customerId === paymentConstants.UNLIMITED_CUSTOMER_ID) { previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_LIFETIME_FREE; } else if (memberPlan.customerId === paymentConstants.GROUP_PLAN_CUSTOMER_ID) { previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GROUP_PLAN; } else { // this triggers a generic message in the email template in case we forget // to update this code for new special cases previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_UNKNOWN; } txnEmail(member, 'group-member-join', [ { name: 'LEADER', content: leader.profile.name }, { name: 'GROUP_NAME', content: group.name }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: previousSubscriptionType }, ]); return; } if (member.hasNotCancelled()) { await member.cancelSubscription({ cancellationReason: JOINED_GROUP_PLAN }); previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL; } const today = new Date(); plan = member.purchased.plan.toObject(); let extraMonths = Number(plan.extraMonths); if (plan.dateTerminated) extraMonths += _dateDiff(today, plan.dateTerminated); _(plan).merge({ // override with these values planId: 'group_plan_auto', customerId: 'group-plan', dateUpdated: today, paymentMethod: 'groupPlan', extraMonths, dateTerminated: null, lastBillingDate: null, owner: member._id, }).defaults({ // allow non-override if a plan was previously used gemsBought: 0, dateCreated: today, mysteryItems: [], }).value(); } // save unused hourglass and mystery items plan.consecutive.trinkets = memberPlan.consecutive.trinkets; plan.mysteryItems = memberPlan.mysteryItems; member.purchased.plan = plan; member.items.mounts['Jackalope-RoyalPurple'] = true; member.markModified('items.mounts'); data.user = member; await createSubscription(data); txnEmail(data.user, 'group-member-join', [ { name: 'LEADER', content: leader.profile.name }, { name: 'GROUP_NAME', content: group.name }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: previousSubscriptionType }, ]); } /** * Cancels subscriptions of members of a group * * @param group The Group Model that is cancelling a group plan * * @return undefined */ async function cancelGroupUsersSubscription (group) { let members; if (group.type === 'guild') { members = await User.find({ guilds: group._id }).select('_id guilds purchased').exec(); } else { members = await User.find({ 'party._id': group._id }).select('_id guilds purchased').exec(); } // eslint-disable-next-line no-use-before-define const promises = members.map(member => cancelGroupSubscriptionForUser(member, group)); await Promise.all(promises); } async function cancelGroupSubscriptionForUser (user, group, userWasRemoved = false) { if (user.purchased.plan.customerId !== paymentConstants.GROUP_PLAN_CUSTOMER_ID) return; const userGroups = user.guilds.toObject(); if (user.party._id) userGroups.push(user.party._id); const index = userGroups.indexOf(group._id); if (index >= 0) userGroups.splice(index, 1); await Tasks.Task.deleteMany({ userId: user._id, 'group.id': group._id }).exec(); const groupPlansQuery = { // type: { $in: ['guild', 'party'] }, // privacy: 'private', _id: { $in: userGroups }, 'purchased.plan.dateTerminated': { $type: 'null' }, }; const groupFields = `${basicGroupFields} purchased`; const userGroupPlans = await Group.find(groupPlansQuery).select(groupFields).exec(); if (userGroupPlans.length === 0) { const leader = await User.findById(group.leader).exec(); const email = userWasRemoved ? 'group-member-removed' : 'group-member-cancel'; txnEmail(user, email, [ { name: 'LEADER', content: leader.profile.name }, { name: 'GROUP_NAME', content: group.name }, ]); await cancelSubscription({ user }); } } export { addSubscriptionToGroupUsers, addSubToGroupUser, cancelGroupUsersSubscription, cancelGroupSubscriptionForUser, };