Group plans subs to all (#8394)

* Added subscriptions to all members when group subs

* Added unsub when group cancels

* Give user a subscription when they join a subbed group

* Removed subscription when user leaves or is removed from group

* Fixed linting issues:

* Added tests for users with a subscription being upgraded to group plan

* Added tests for checking if existing recurring user sub gets updated during group plan. Added better merging for plans

* Added test for existing gift subscriptions

* Added additional months to user when they have an existing recurring subscription and get upgraded to group sub

* Adds test for user who has cancelled with date termined in the future

* Added test to ensure date termined is reset

* Added tests for extra months carrying over

* Added test for gems bought field

* Add tests to for fields that should remain when upgrading

* Added test for all payment methods

* Added prevention for when a user joins a second group plan

* Fixed subscribing tests

* Separated group plan payment tests

* Added prevention of editing a user with a unlimited sub

* Add tests to ensure group keeps plan if they are in two and leave one

* Ensured users with two group plans do not get cancelled when on group plan is cancelled

* Ensured users without group sub are untouched when group cancels

* Fixed lint issues

* Added new emails

* Added fix for cron tests

* Add restore to stubbed methods

* Ensured cancelled group subscriptions are updated

* Changed group plan exist check to check for date terminated

* Updated you cannont delete active group message

* Removed description requirement

* Added upgrade group plan for Amazon payments

* Fixed lint issues

* Fixed broken tests

* Fixed user delete tests

* Fixed function calls

* Hid cancel button if user has group plan

* Hide difficulty from rewards

* Prevented add user functions to be called when group plan is cancelled

* Fixed merge issue

* Correctly displayed group price

* Added message when you are about to join canclled group plan

* Fixed linting issues

* Updated tests to have no redirect to homes

* Allowed leaving a group with a canceld subscription

* Fixed spelling issues

* Prevented user from changing leader with active sub

* Added payment details title to replace subscription title

* Ensured we do not count leader when displaying upcoming cost

* Prevented party tasks from being displayed twice

* Prevented cancelling and already cancelled sub

* Fixed styles of subscriptions

* Added more specific mystery item tests

* Fixed test to refer to leader

* Extended test range to account for short months

* Fixed merge conflicts

* Updated yarn file

* Added missing locales

* Trigger notification

* Removed yarn

* Fixed locales

* Fixed scope mispelling

* Fixed line endings

* Removed extra advanced options from rewards

* Prevent group leader from leaving an active group plan

* Fixed issue with extra months applied to cancelled group plan

* Ensured member count is calculated when updatedGroupPlan

* Updated amazon payment method constant name

* Added comment to cancel sub user method

* Fixed smantic issues

* Added unite test for user isSubscribed and hasNotCancelled

* Add tests for isSubscribed and hasNotCanceled

* Changed default days remaining to 2 days for group plans

* Fixed logic with adding canceled notice to group invite
This commit is contained in:
Keith Holliday
2017-03-06 15:09:50 -07:00
committed by GitHub
parent 03a1d61c08
commit be60fb0635
33 changed files with 2128 additions and 668 deletions

View File

@@ -11,19 +11,21 @@ import {
model as Group,
basicFields as basicGroupFields,
} from '../models/group';
import { model as User } from '../models/user';
import {
NotAuthorized,
NotFound,
} from './errors';
import slack from './slack';
import nconf from 'nconf';
import stripeModule from 'stripe';
const stripe = stripeModule(nconf.get('STRIPE_API_KEY'));
let api = {};
api.constants = {
UNLIMITED_CUSTOMER_ID: 'habitrpg', // Users with the customerId have an unlimted free subscription
GROUP_PLAN_CUSTOMER_ID: 'group-plan',
GROUP_PLAN_PAYMENT_METHOD: 'Group Plan',
};
function revealMysteryItems (user) {
_.each(shared.content.gear.flat, function findMysteryItems (item) {
if (
@@ -44,6 +46,158 @@ function _dateDiff (earlyDate, lateDate) {
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
*/
api.addSubscriptionToGroupUsers = async function addSubscriptionToGroupUsers (group) {
let members;
if (group.type === 'guild') {
members = await User.find({guilds: group._id}).select('_id purchased').exec();
} else {
members = await User.find({'party._id': group._id}).select('_id purchased').exec();
}
let promises = members.map((member) => {
return this.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
*/
api.addSubToGroupUser = async function addSubToGroupUser (member, group) {
let customerIdsToIgnore = [this.constants.GROUP_PLAN_CUSTOMER_ID, this.constants.UNLIMITED_CUSTOMER_ID];
let 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: new Date(),
mysteryItems: [],
consecutive: {
trinkets: 0,
offset: 0,
gemCapExtra: 0,
},
};
if (member.isSubscribed()) {
let memberPlan = member.purchased.plan;
let customerHasCancelledGroupPlan = memberPlan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID && !member.hasNotCancelled();
if (customerIdsToIgnore.indexOf(memberPlan.customerId) !== -1 && !customerHasCancelledGroupPlan) return;
if (member.hasNotCancelled()) await member.cancelSubscription();
let today = new Date();
plan = _.clone(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();
}
member.purchased.plan = plan;
data.user = member;
await this.createSubscription(data);
let leader = await User.findById(group.leader).exec();
txnEmail(data.user, 'group-member-joining', [
{name: 'LEADER', content: leader.profile.name},
{name: 'GROUP_NAME', content: group.name},
]);
};
/**
* Cancels subscriptions of members of a group
*
* @param group The Group Model that is cancelling a group plan
*
* @return undefined
*/
api.cancelGroupUsersSubscription = 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();
}
let promises = members.map((member) => {
return this.cancelGroupSubscriptionForUser(member, group);
});
await Promise.all(promises);
};
api.cancelGroupSubscriptionForUser = async function cancelGroupSubscriptionForUser (user, group) {
if (user.purchased.plan.customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) return;
let userGroups = _.clone(user.guilds.toObject());
userGroups.push('party');
let index = userGroups.indexOf(group._id);
userGroups.splice(index, 1);
let groupPlansQuery = {
type: {$in: ['guild', 'party']},
// privacy: 'private',
_id: {$in: userGroups},
'purchased.plan.dateTerminated': null,
};
let groupFields = `${basicGroupFields} purchased`;
let userGroupPlans = await Group.find(groupPlansQuery).select(groupFields).exec();
if (userGroupPlans.length === 0) {
let leader = await User.findById(group.leader).exec();
txnEmail(user, 'group-member-cancel', [
{name: 'LEADER', content: leader.profile.name},
{name: 'GROUP_NAME', content: group.name},
]);
await this.cancelSubscription({user});
}
};
api.createSubscription = async function createSubscription (data) {
let recipient = data.gift ? data.gift.member : data.user;
let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
@@ -75,6 +229,8 @@ api.createSubscription = async function createSubscription (data) {
emailType = 'group-subscription-begins';
groupId = group._id;
recipient.purchased.plan.quantity = data.sub.quantity;
await this.addSubscriptionToGroupUsers(group);
}
plan = recipient.purchased.plan;
@@ -135,7 +291,8 @@ api.createSubscription = async function createSubscription (data) {
revealMysteryItems(recipient);
}
if (!data.gift) {
// @TODO: Create a factory pattern for use cases
if (!data.gift && data.customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) {
txnEmail(data.user, emailType);
}
@@ -225,22 +382,6 @@ api.createSubscription = async function createSubscription (data) {
});
};
api.updateStripeGroupPlan = async function updateStripeGroupPlan (group, stripeInc) {
if (group.purchased.plan.paymentMethod !== 'Stripe') return;
let stripeApi = stripeInc || stripe;
let plan = shared.content.subscriptionBlocks.group_monthly;
await stripeApi.subscriptions.update(
group.purchased.plan.subscriptionId,
{
plan: plan.key,
quantity: group.memberCount + plan.quantity - 1,
}
);
group.purchased.plan.quantity = group.memberCount + plan.quantity - 1;
};
// Sets their subscription to be cancelled later
api.cancelSubscription = async function cancelSubscription (data) {
let plan;
@@ -248,6 +389,7 @@ api.cancelSubscription = async function cancelSubscription (data) {
let cancelType = 'unsubscribe';
let groupId;
let emailType = 'cancel-subscription';
let emailMergeData = [];
// If we are buying a group subscription
if (data.groupId) {
@@ -265,12 +407,23 @@ api.cancelSubscription = async function cancelSubscription (data) {
}
plan = group.purchased.plan;
emailType = 'group-cancel-subscription';
emailMergeData.push({name: 'GROUP_NAME', content: group.name});
await this.cancelGroupUsersSubscription(group);
} else {
plan = data.user.purchased.plan;
}
let customerId = plan.customerId;
let now = moment();
let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30;
let defaultRemainingDays = 30;
if (plan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID) {
defaultRemainingDays = 2;
}
let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : defaultRemainingDays;
if (plan.extraMonths < 0) plan.extraMonths = 0;
let extraDays = Math.ceil(30.5 * plan.extraMonths);
let nowStr = `${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`;
let nowStrFormat = 'MM/DD/YYYY';
@@ -289,7 +442,7 @@ api.cancelSubscription = async function cancelSubscription (data) {
await data.user.save();
}
txnEmail(data.user, emailType);
if (customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) txnEmail(data.user, emailType, emailMergeData);
if (group) {
cancelType = 'group-unsubscribe';