From 5f468d16b7a827dea5f74e7ddcfac92554573ba1 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 20 Nov 2017 12:34:41 -0600 Subject: [PATCH] Cancel users free group plan when they leave a group (#9543) * Cancel users free group plan when they leave a group * Fixed lint --- .../groups/POST-groups_groupId_leave.js | 43 +++++++++++++++++++ website/server/controllers/api-v3/groups.js | 15 +++++-- website/server/models/user/methods.js | 30 +++++++++---- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js index c6b2c96193..403baf5da7 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_leave.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.js @@ -10,6 +10,8 @@ import { v4 as generateUUID } from 'uuid'; import { each, } from 'lodash'; +import { model as User } from '../../../../../website/server/models/user'; +import * as payments from '../../../../../website/server/libs/payments'; describe('POST /groups/:groupId/leave', () => { let typesOfGroups = { @@ -264,4 +266,45 @@ describe('POST /groups/:groupId/leave', () => { expect(userWithNonExistentParty.party).to.eql({}); }); }); + + context('Leaving a group plan', () => { + it('cancels the free subscription', async () => { + // Create group + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Private Guild', + type: 'guild', + }, + members: 1, + }); + + let leader = groupLeader; + let member = members[0]; + let userWithFreePlan = await User.findById(leader._id).exec(); + + // Create subscription + let paymentData = { + user: userWithFreePlan, + groupId: group._id, + sub: { + key: 'basic_3mo', + }, + customerId: 'customer-id', + paymentMethod: 'Payment Method', + headers: { + 'x-client': 'habitica-web', + 'user-agent': '', + }, + }; + await payments.createSubscription(paymentData); + await member.sync(); + expect(member.purchased.plan.planId).to.equal('group_plan_auto'); + expect(member.purchased.plan.dateTerminated).to.not.exist; + + // Leave + await member.post(`/groups/${group._id}/leave`); + await member.sync(); + expect(member.purchased.plan.dateTerminated).to.exist; + }); + }); }); diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index ec7c8e549e..7ffea80783 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -755,13 +755,20 @@ api.leaveGroup = { } await group.leave(user, req.query.keep, req.body.keepChallenges); - - if (group.hasNotCancelled()) await group.updateGroupPlan(true); - + if (group.hasNotCancelled()) await group.updateGroupPlan(true); _removeMessagesFromMember(user, group._id); - await user.save(); + if (group.type !== 'party') { + let guildIndex = user.guilds.indexOf(group._id); + user.guilds.splice(guildIndex, 1); + } + + let isMemberOfGroupPlan = await user.isMemberOfGroupPlan(); + if (!isMemberOfGroupPlan) { + await payments.cancelGroupSubscriptionForUser(user, group); + } + res.respond(200, {}); }, }; diff --git a/website/server/models/user/methods.js b/website/server/models/user/methods.js index ab311c2dad..d58aca2761 100644 --- a/website/server/models/user/methods.js +++ b/website/server/models/user/methods.js @@ -275,6 +275,19 @@ schema.methods.daysUserHasMissed = function daysUserHasMissed (now, req = {}) { return {daysMissed, timezoneOffsetFromUserPrefs}; }; +async function getUserGroupData (user) { + const userGroups = user.getGroups(); + + const groups = await Group + .find({ + _id: {$in: userGroups}, + }) + .select('leaderOnly leader purchased') + .exec(); + + return groups; +} + // Determine if the user can get gems: some groups restrict their members ability to obtain them. // User is allowed to buy gems if no group has `leaderOnly.getGems` === true or if // its the group leader @@ -286,16 +299,17 @@ schema.methods.canGetGems = async function canObtainGems () { return true; } - const userGroups = user.getGroups(); - - const groups = await Group - .find({ - _id: {$in: userGroups}, - }) - .select('leaderOnly leader purchased') - .exec(); + const groups = await getUserGroupData(user); return groups.every(g => { return !g.isSubscribed() || g.leader === user._id || g.leaderOnly.getGems !== true; }); }; + +schema.methods.isMemberOfGroupPlan = async function isMemberOfGroupPlan () { + const groups = await getUserGroupData(this); + + return groups.every(g => { + return g.isSubscribed(); + }); +};