mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
fix(server-api): cancelSubscription() is no longer called twice when user leaves group
This commit is contained in:
@@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid';
|
|||||||
import {
|
import {
|
||||||
each,
|
each,
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
generateChallenge,
|
generateChallenge,
|
||||||
checkExistence,
|
checkExistence,
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import payments from '../../../../../website/server/libs/payments/payments';
|
import payments from '../../../../../website/server/libs/payments/payments';
|
||||||
|
import { calculateSubscriptionTerminationDate } from '../../../../../website/server/libs/payments/util';
|
||||||
|
|
||||||
describe('POST /groups/:groupId/leave', () => {
|
describe('POST /groups/:groupId/leave', () => {
|
||||||
const typesOfGroups = {
|
const typesOfGroups = {
|
||||||
@@ -338,4 +340,48 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
|
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
|
||||||
|
const extraMonths = 12;
|
||||||
|
let groupWithPlan;
|
||||||
|
let member;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const { group, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails,
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
});
|
||||||
|
[member] = members;
|
||||||
|
groupWithPlan = group;
|
||||||
|
await member.update({
|
||||||
|
'purchased.plan.extraMonths': extraMonths,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('calculates dateTerminated and sets extraMonths to zero after user leaves the group', async () => {
|
||||||
|
const userBeforeLeave = await User.findById(member._id).exec();
|
||||||
|
|
||||||
|
await member.post(`/groups/${groupWithPlan._id}/leave`);
|
||||||
|
const userAfterLeave = await User.findById(member._id).exec();
|
||||||
|
|
||||||
|
const dateTerminatedBefore = userBeforeLeave.purchased.plan.dateTerminated;
|
||||||
|
const extraMonthsBefore = userBeforeLeave.purchased.plan.extraMonths;
|
||||||
|
const dateTerminatedAfter = userAfterLeave.purchased.plan.dateTerminated;
|
||||||
|
const extraMonthsAfter = userAfterLeave.purchased.plan.extraMonths;
|
||||||
|
|
||||||
|
const expectedTerminationDate = calculateSubscriptionTerminationDate(null, {
|
||||||
|
customerId: payments.constants.GROUP_PLAN_CUSTOMER_ID,
|
||||||
|
extraMonths,
|
||||||
|
}, payments.constants);
|
||||||
|
|
||||||
|
expect(extraMonthsBefore).to.gte(12);
|
||||||
|
expect(extraMonthsAfter).to.equal(0);
|
||||||
|
expect(dateTerminatedBefore).to.be.null;
|
||||||
|
expect(dateTerminatedAfter).to.exist;
|
||||||
|
|
||||||
|
expect(moment(dateTerminatedAfter).diff(expectedTerminationDate, 'days')).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -5,6 +5,8 @@ import { v4 as generateUUID } from 'uuid';
|
|||||||
import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes';
|
import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes';
|
||||||
import { requester } from '../requester';
|
import { requester } from '../requester';
|
||||||
import * as Tasks from '../../../../website/server/models/task';
|
import * as Tasks from '../../../../website/server/models/task';
|
||||||
|
import payments from '../../../../website/server/libs/payments/payments';
|
||||||
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
|
|
||||||
// Creates a new user and returns it
|
// Creates a new user and returns it
|
||||||
// If you need the user to have specific requirements,
|
// If you need the user to have specific requirements,
|
||||||
@@ -83,14 +85,35 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
|||||||
return apiGroup;
|
return apiGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _upgradeToGroupPlan (groupLeader, group) {
|
||||||
|
const groupLeaderModel = await User.findById(groupLeader._id).exec();
|
||||||
|
|
||||||
|
// Create subscription
|
||||||
|
const paymentData = {
|
||||||
|
user: groupLeaderModel,
|
||||||
|
groupId: group._id,
|
||||||
|
sub: {
|
||||||
|
key: 'basic_3mo',
|
||||||
|
},
|
||||||
|
customerId: 'customer-id',
|
||||||
|
paymentMethod: 'Payment Method',
|
||||||
|
headers: {
|
||||||
|
'x-client': 'habitica-web',
|
||||||
|
'user-agent': '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await payments.createSubscription(paymentData);
|
||||||
|
}
|
||||||
|
|
||||||
// This is generate group + the ability to create
|
// This is generate group + the ability to create
|
||||||
// real users to populate it. The settings object
|
// real users to populate it. The settings object
|
||||||
// takes in:
|
// takes in:
|
||||||
// members: Number - the number of group members to create.
|
// members: Number - the number of group members to create.
|
||||||
// Defaults to 0. Does not include group leader.
|
// Defaults to 0. Does not include group leader.
|
||||||
// inivtes: Number - the number of users to create and invite to the group. Defaults to 0.
|
// invites: Number - the number of users to create and invite to the group. Defaults to 0.
|
||||||
// groupDetails: Object - how to initialize the group
|
// groupDetails: Object - how to initialize the group
|
||||||
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
|
// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user
|
||||||
|
// addGroupPlan: boolean - will add group plan with basic subscription. Defaults to false
|
||||||
// can create the group
|
// can create the group
|
||||||
//
|
//
|
||||||
// Returns an object with
|
// Returns an object with
|
||||||
@@ -101,6 +124,7 @@ export async function generateGroup (leader, details = {}, update = {}) {
|
|||||||
export async function createAndPopulateGroup (settings = {}) {
|
export async function createAndPopulateGroup (settings = {}) {
|
||||||
const numberOfMembers = settings.members || 0;
|
const numberOfMembers = settings.members || 0;
|
||||||
const numberOfInvites = settings.invites || 0;
|
const numberOfInvites = settings.invites || 0;
|
||||||
|
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||||
const { groupDetails } = settings;
|
const { groupDetails } = settings;
|
||||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||||
|
|
||||||
@@ -130,6 +154,10 @@ export async function createAndPopulateGroup (settings = {}) {
|
|||||||
|
|
||||||
await Promise.all(invitees.map(invitee => invitee.sync()));
|
await Promise.all(invitees.map(invitee => invitee.sync()));
|
||||||
|
|
||||||
|
if (upgradeToGroupPlan) {
|
||||||
|
await _upgradeToGroupPlan(groupLeader, group);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupLeader,
|
groupLeader,
|
||||||
group,
|
group,
|
||||||
|
|||||||
@@ -861,11 +861,6 @@ api.leaveGroup = {
|
|||||||
if (guildIndex >= 0) user.guilds.splice(guildIndex, 1);
|
if (guildIndex >= 0) user.guilds.splice(guildIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMemberOfGroupPlan = await user.isMemberOfGroupPlan();
|
|
||||||
if (!isMemberOfGroupPlan) {
|
|
||||||
await payments.cancelGroupSubscriptionForUser(user, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (group.hasNotCancelled()) await group.updateGroupPlan(true);
|
if (group.hasNotCancelled()) await group.updateGroupPlan(true);
|
||||||
res.respond(200, {});
|
res.respond(200, {});
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user