2022-10-27 06:39:06 +00:00
// TODO these files need to refactored.
2018-05-06 20:12:00 +00:00
import nconf from 'nconf' ;
import _ from 'lodash' ;
import moment from 'moment' ;
2019-10-10 18:11:50 +00:00
import { model as User } from '../../models/user' ; // eslint-disable-line import/no-cycle
2021-02-04 20:42:48 +00:00
import * as Tasks from '../../models/task' ; // eslint-disable-line import/no-cycle
2019-10-10 18:11:50 +00:00
import { // eslint-disable-line import/no-cycle
2018-05-06 20:12:00 +00:00
model as Group ,
basicFields as basicGroupFields ,
} from '../../models/group' ;
2019-10-10 18:11:50 +00:00
import { // eslint-disable-line import/no-cycle
2018-05-06 20:12:00 +00:00
getUserInfo ,
sendTxn as txnEmail ,
} from '../email' ;
2022-10-27 06:39:06 +00:00
import { paymentConstants } from './constants' ;
import { cancelSubscription , createSubscription } from './subscriptions' ; // eslint-disable-line import/no-cycle
2018-05-06 20:12:00 +00:00
2018-12-06 16:13:49 +00:00
const TECH _ASSISTANCE _EMAIL = nconf . get ( 'EMAILS_TECH_ASSISTANCE_EMAIL' ) ;
2018-05-06 20:12:00 +00:00
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' ) {
2019-10-08 14:57:10 +00:00
members = await User . find ( { guilds : group . _id } ) . select ( '_id purchased items auth profile.name notifications' ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
} else {
2019-10-08 14:57:10 +00:00
members = await User . find ( { 'party._id' : group . _id } ) . select ( '_id purchased items auth profile.name notifications' ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
}
2022-10-27 06:39:06 +00:00
// eslint-disable-next-line no-use-before-define
const promises = members . map ( member => addSubToGroupUser ( member , group ) ) ;
2018-05-06 20:12:00 +00:00
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.
2019-10-10 18:11:50 +00:00
const customerIdsToIgnore = [
2022-10-27 06:39:06 +00:00
paymentConstants . GROUP _PLAN _CUSTOMER _ID ,
paymentConstants . UNLIMITED _CUSTOMER _ID ,
2019-10-10 18:11:50 +00:00
] ;
const paymentMethodsToIgnore = [
2022-10-27 06:39:06 +00:00
paymentConstants . GOOGLE _PAYMENT _METHOD ,
paymentConstants . IOS _PAYMENT _METHOD ,
2019-10-10 18:11:50 +00:00
] ;
2018-05-06 20:12:00 +00:00
let previousSubscriptionType = EMAIL _TEMPLATE _SUBSCRIPTION _TYPE _NONE ;
2019-10-08 14:57:10 +00:00
const leader = await User . findById ( group . leader ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
2019-10-08 14:57:10 +00:00
const data = {
2018-05-06 20:12:00 +00:00
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 ,
2020-11-02 10:42:09 +00:00
dateCreated : member . purchased . plan . dateCreated || new Date ( ) ,
2018-05-06 20:12:00 +00:00
mysteryItems : [ ] ,
consecutive : {
trinkets : 0 ,
offset : 0 ,
gemCapExtra : 0 ,
} ,
} ;
2019-10-08 14:57:10 +00:00
const memberPlan = member . purchased . plan ;
2018-05-06 20:12:00 +00:00
if ( member . isSubscribed ( ) ) {
2019-10-10 18:11:50 +00:00
const customerHasCancelledGroupPlan = (
2022-10-27 06:39:06 +00:00
memberPlan . customerId === paymentConstants . GROUP _PLAN _CUSTOMER _ID
2019-10-10 18:11:50 +00:00
&& ! member . hasNotCancelled ( )
) ;
2019-10-08 14:57:10 +00:00
const ignorePaymentPlan = paymentMethodsToIgnore . indexOf ( memberPlan . paymentMethod ) !== - 1 ;
const ignoreCustomerId = customerIdsToIgnore . indexOf ( memberPlan . customerId ) !== - 1 ;
2018-05-06 20:12:00 +00:00
if ( ignorePaymentPlan ) {
2019-10-08 14:57:10 +00:00
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.' } ,
2019-10-10 18:11:50 +00:00
// 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.
2018-05-06 20:12:00 +00:00
] ) ;
}
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
2022-10-27 06:39:06 +00:00
if ( memberPlan . paymentMethod === paymentConstants . GOOGLE _PAYMENT _METHOD ) {
2018-05-06 20:12:00 +00:00
previousSubscriptionType = EMAIL _TEMPLATE _SUBSCRIPTION _TYPE _GOOGLE ;
2022-10-27 06:39:06 +00:00
} else if ( memberPlan . paymentMethod === paymentConstants . IOS _PAYMENT _METHOD ) {
2018-05-06 20:12:00 +00:00
previousSubscriptionType = EMAIL _TEMPLATE _SUBSCRIPTION _TYPE _IOS ;
2022-10-27 06:39:06 +00:00
} else if ( memberPlan . customerId === paymentConstants . UNLIMITED _CUSTOMER _ID ) {
2018-05-06 20:12:00 +00:00
previousSubscriptionType = EMAIL _TEMPLATE _SUBSCRIPTION _TYPE _LIFETIME _FREE ;
2022-10-27 06:39:06 +00:00
} else if ( memberPlan . customerId === paymentConstants . GROUP _PLAN _CUSTOMER _ID ) {
2018-05-06 20:12:00 +00:00
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' , [
2019-10-08 14:57:10 +00:00
{ name : 'LEADER' , content : leader . profile . name } ,
{ name : 'GROUP_NAME' , content : group . name } ,
{ name : 'PREVIOUS_SUBSCRIPTION_TYPE' , content : previousSubscriptionType } ,
2018-05-06 20:12:00 +00:00
] ) ;
return ;
}
if ( member . hasNotCancelled ( ) ) {
2019-10-08 14:57:10 +00:00
await member . cancelSubscription ( { cancellationReason : JOINED _GROUP _PLAN } ) ;
2018-05-06 20:12:00 +00:00
previousSubscriptionType = EMAIL _TEMPLATE _SUBSCRIPTION _TYPE _NORMAL ;
}
2019-10-08 14:57:10 +00:00
const today = new Date ( ) ;
2018-05-06 20:12:00 +00:00
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
2023-05-24 18:29:42 +00:00
plan . perkMonthCount = memberPlan . perkMonthCount ;
2018-05-06 20:12:00 +00:00
plan . consecutive . trinkets = memberPlan . consecutive . trinkets ;
plan . mysteryItems = memberPlan . mysteryItems ;
member . purchased . plan = plan ;
member . items . mounts [ 'Jackalope-RoyalPurple' ] = true ;
2019-04-01 17:24:18 +00:00
member . markModified ( 'items.mounts' ) ;
2018-05-06 20:12:00 +00:00
data . user = member ;
2022-10-27 06:39:06 +00:00
await createSubscription ( data ) ;
2018-05-06 20:12:00 +00:00
txnEmail ( data . user , 'group-member-join' , [
2019-10-08 14:57:10 +00:00
{ name : 'LEADER' , content : leader . profile . name } ,
{ name : 'GROUP_NAME' , content : group . name } ,
{ name : 'PREVIOUS_SUBSCRIPTION_TYPE' , content : previousSubscriptionType } ,
2018-05-06 20:12:00 +00:00
] ) ;
}
/ * *
* 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' ) {
2019-10-08 14:57:10 +00:00
members = await User . find ( { guilds : group . _id } ) . select ( '_id guilds purchased' ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
} else {
2019-10-08 14:57:10 +00:00
members = await User . find ( { 'party._id' : group . _id } ) . select ( '_id guilds purchased' ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
}
2022-10-27 06:39:06 +00:00
// eslint-disable-next-line no-use-before-define
const promises = members . map ( member => cancelGroupSubscriptionForUser ( member , group ) ) ;
2018-05-06 20:12:00 +00:00
await Promise . all ( promises ) ;
}
async function cancelGroupSubscriptionForUser ( user , group , userWasRemoved = false ) {
2022-10-27 06:39:06 +00:00
if ( user . purchased . plan . customerId !== paymentConstants . GROUP _PLAN _CUSTOMER _ID ) return ;
2018-05-06 20:12:00 +00:00
2019-10-08 14:57:10 +00:00
const userGroups = user . guilds . toObject ( ) ;
2018-10-09 18:07:50 +00:00
if ( user . party . _id ) userGroups . push ( user . party . _id ) ;
2018-05-06 20:12:00 +00:00
2019-10-08 14:57:10 +00:00
const index = userGroups . indexOf ( group . _id ) ;
2019-08-15 15:06:56 +00:00
if ( index >= 0 ) userGroups . splice ( index , 1 ) ;
2018-05-06 20:12:00 +00:00
2021-02-04 20:42:48 +00:00
await Tasks . Task . remove ( { userId : user . _id , 'group.id' : group . _id } ) . exec ( ) ;
2019-10-08 14:57:10 +00:00
const groupPlansQuery = {
2019-08-15 15:06:56 +00:00
// type: { $in: ['guild', 'party'] },
2018-05-06 20:12:00 +00:00
// privacy: 'private',
2019-10-08 14:57:10 +00:00
_id : { $in : userGroups } ,
2019-08-15 15:06:56 +00:00
'purchased.plan.dateTerminated' : { $type : 'null' } ,
2018-05-06 20:12:00 +00:00
} ;
2019-10-08 14:57:10 +00:00
const groupFields = ` ${ basicGroupFields } purchased ` ;
const userGroupPlans = await Group . find ( groupPlansQuery ) . select ( groupFields ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
2019-10-08 14:57:10 +00:00
if ( userGroupPlans . length === 0 ) {
const leader = await User . findById ( group . leader ) . exec ( ) ;
2018-05-06 20:12:00 +00:00
const email = userWasRemoved ? 'group-member-removed' : 'group-member-cancel' ;
txnEmail ( user , email , [
2019-10-08 14:57:10 +00:00
{ name : 'LEADER' , content : leader . profile . name } ,
{ name : 'GROUP_NAME' , content : group . name } ,
2018-05-06 20:12:00 +00:00
] ) ;
2022-10-27 06:39:06 +00:00
await cancelSubscription ( { user } ) ;
2018-05-06 20:12:00 +00:00
}
}
2019-10-02 17:45:27 +00:00
export {
2018-05-06 20:12:00 +00:00
addSubscriptionToGroupUsers ,
addSubToGroupUser ,
cancelGroupUsersSubscription ,
cancelGroupSubscriptionForUser ,
} ;