mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
use new email template when joining a group plan for customisation of subscription cancellation information (#8637)
* use new email template when subscription is cancelled from joining a group plan * use new email template when subscription is cancelled from joining a group plan - needs more code, tests * change from sending new email as a cancel-subscription option to sending as a group plan join email Uses a new group-member-join email template instead of old group-member-joining because new template includes mandril conditional merge tags. Also adds tests and comments. Edits some comments for accuracy and typo fixes. * adapt group-member-join email template for manual cancel message for iOS and Android subscriptions * save test user so its profile name can be read by calls to sendTxn * add documentation for the user model cancelSubscription function * add constants for strings passed to mandrill email templates
This commit is contained in:
@@ -525,6 +525,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
@@ -555,6 +556,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
amzLib.closeBillingAgreement.restore();
|
||||
});
|
||||
@@ -593,6 +595,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(group.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
expectAmazonStubs();
|
||||
});
|
||||
@@ -623,6 +626,7 @@ describe('Amazon Payments', () => {
|
||||
nextBill: moment(group.purchased.plan.lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: amzLib.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
amzLib.closeBillingAgreement.restore();
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('payments/index', () => {
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
await user.save();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
|
||||
@@ -138,7 +138,7 @@ describe('Canceling a subscription for group', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('prevents non group leader from manging subscription', async () => {
|
||||
it('prevents non group leader from managing subscription', async () => {
|
||||
let groupMember = new User();
|
||||
data.user = groupMember;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import moment from 'moment';
|
||||
import stripeModule from 'stripe';
|
||||
import nconf from 'nconf';
|
||||
|
||||
import * as sender from '../../../../../../../website/server/libs/email';
|
||||
import * as api from '../../../../../../../website/server/libs/payments';
|
||||
@@ -12,17 +13,24 @@ import {
|
||||
generateGroup,
|
||||
} from '../../../../../../helpers/api-unit.helper.js';
|
||||
|
||||
describe('Purchasing a subscription for group', () => {
|
||||
describe('Purchasing a group plan for group', () => {
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS = 'iOS_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL = 'normal_subscription';
|
||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE = 'no_subscription';
|
||||
|
||||
let plan, group, user, data;
|
||||
let stripe = stripeModule('test');
|
||||
let groupLeaderName = 'sender';
|
||||
let groupName = 'test group';
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
user.profile.name = 'sender';
|
||||
user.profile.name = groupLeaderName;
|
||||
await user.save();
|
||||
|
||||
group = generateGroup({
|
||||
name: 'test group',
|
||||
name: groupName,
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
leader: user._id,
|
||||
@@ -81,7 +89,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
sender.sendTxn.restore();
|
||||
});
|
||||
|
||||
it('creates a subscription', async () => {
|
||||
it('creates a group plan', async () => {
|
||||
expect(group.purchased.plan.planId).to.not.exist;
|
||||
data.groupId = group._id;
|
||||
|
||||
@@ -157,7 +165,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
expect(updatedLeader.items.mounts['Jackalope-RoyalPurple']).to.be.true;
|
||||
});
|
||||
|
||||
it('sends an email to members of group', async () => {
|
||||
it('sends an email to member of group who was not a subscriber', async () => {
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.guilds.push(group._id);
|
||||
@@ -169,11 +177,181 @@ describe('Purchasing a subscription for group', () => {
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-joining');
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE},
|
||||
]);
|
||||
// confirm that the other email sent is appropriate:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => {
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.key = 'basic_earned';
|
||||
plan.paymentMethod = stripePayments.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||
.returnsPromise()
|
||||
.resolves({
|
||||
BillingAgreementDetails: {
|
||||
BillingAgreementStatus: {State: 'Closed'},
|
||||
},
|
||||
});
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.planId = 'basic_earned';
|
||||
plan.paymentMethod = amzLib.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
amzLib.getBillingAgreementDetails.restore();
|
||||
});
|
||||
|
||||
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||
.returnsPromise().resolves({
|
||||
agreement_details: { // eslint-disable-line camelcase
|
||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||
cycles_completed: 1, // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
plan.planId = 'basic_earned';
|
||||
plan.paymentMethod = paypalPayments.constants.PAYMENT_METHOD;
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledTwice;
|
||||
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.firstCall.args[2]).to.eql([
|
||||
{name: 'LEADER', content: user.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL},
|
||||
]);
|
||||
// confirm that the other email sent is not a cancel-subscription email:
|
||||
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins');
|
||||
|
||||
paypalPayments.paypalBillingAgreementGet.restore();
|
||||
paypalPayments.paypalBillingAgreementCancel.restore();
|
||||
});
|
||||
|
||||
it('sends appropriate emails when subscribed member of group must manually cancel recurring Android subscription', async () => {
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledFourTimes;
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
|
||||
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[1][2]).to.eql([
|
||||
{name: 'LEADER', content: groupLeaderName},
|
||||
{name: 'GROUP_NAME', content: groupName},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE},
|
||||
]);
|
||||
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('sends appropriate emails when subscribed member of group must manually cancel recurring iOS subscription', async () => {
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD;
|
||||
|
||||
let recipient = new User();
|
||||
recipient.profile.name = 'recipient';
|
||||
recipient.purchased.plan = plan;
|
||||
recipient.guilds.push(group._id);
|
||||
await recipient.save();
|
||||
|
||||
user.guilds.push(group._id);
|
||||
await user.save();
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(sender.sendTxn).to.be.calledFourTimes;
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
|
||||
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
|
||||
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[1][2]).to.eql([
|
||||
{name: 'LEADER', content: groupLeaderName},
|
||||
{name: 'GROUP_NAME', content: groupName},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS},
|
||||
]);
|
||||
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
|
||||
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
|
||||
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
|
||||
});
|
||||
|
||||
it('adds months to members with existing gift subscription', async () => {
|
||||
@@ -333,7 +511,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription (Android)');
|
||||
it('adds months to members with existing recurring subscription (iOs)');
|
||||
it('adds months to members with existing recurring subscription (iOS)');
|
||||
|
||||
it('adds months to members who already cancelled but not yet terminated recurring subscription', async () => {
|
||||
let recipient = new User();
|
||||
@@ -603,7 +781,7 @@ describe('Purchasing a subscription for group', () => {
|
||||
expect(updatedUser.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('does not modify a user with a Google subscription', async () => {
|
||||
it('does not modify a user with an Android subscription', async () => {
|
||||
plan.customerId = 'random';
|
||||
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
|
||||
|
||||
|
||||
@@ -447,6 +447,7 @@ describe('Paypal Payments', () => {
|
||||
groupId,
|
||||
paymentMethod: 'Paypal',
|
||||
nextBill: nextBillingDate,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -464,6 +465,7 @@ describe('Paypal Payments', () => {
|
||||
groupId: group._id,
|
||||
paymentMethod: 'Paypal',
|
||||
nextBill: nextBillingDate,
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -683,6 +683,7 @@ describe('Stripe Payments', () => {
|
||||
groupId: undefined,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -702,6 +703,7 @@ describe('Stripe Payments', () => {
|
||||
groupId,
|
||||
nextBill: currentPeriodEndTimeStamp * 1000, // timestamp in seconds
|
||||
paymentMethod: 'Stripe',
|
||||
cancellationReason: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,11 +161,12 @@ api.checkout = async function checkout (options = {}) {
|
||||
* @param options.user The user object who is canceling
|
||||
* @param options.groupId The id of the group that is canceling
|
||||
* @param options.headers The request headers
|
||||
* @param options.cancellationReason A text string to control sending an email
|
||||
*
|
||||
* @return undefined
|
||||
*/
|
||||
api.cancelSubscription = async function cancelSubscription (options = {}) {
|
||||
let {user, groupId, headers} = options;
|
||||
let {user, groupId, headers, cancellationReason} = options;
|
||||
|
||||
let billingAgreementId;
|
||||
let planId;
|
||||
@@ -218,6 +219,7 @@ api.cancelSubscription = async function cancelSubscription (options = {}) {
|
||||
nextBill: moment(lastBillingDate).add({ days: subscriptionLength }),
|
||||
paymentMethod: this.constants.PAYMENT_METHOD,
|
||||
headers,
|
||||
cancellationReason,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import slack from './slack';
|
||||
|
||||
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
|
||||
const JOINED_GROUP_PLAN = 'joined group plan';
|
||||
|
||||
let api = {};
|
||||
|
||||
@@ -81,8 +82,22 @@ api.addSubscriptionToGroupUsers = async function addSubscriptionToGroupUsers (gr
|
||||
* @return undefined
|
||||
*/
|
||||
api.addSubToGroupUser = 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.
|
||||
let customerIdsToIgnore = [this.constants.GROUP_PLAN_CUSTOMER_ID, this.constants.UNLIMITED_CUSTOMER_ID];
|
||||
let paymentMethodsToIgnore = [this.constants.GOOGLE_PAYMENT_METHOD, this.constants.IOS_PAYMENT_METHOD];
|
||||
let previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE;
|
||||
let leader = await User.findById(group.leader).exec();
|
||||
|
||||
let data = {
|
||||
user: {},
|
||||
@@ -125,13 +140,39 @@ api.addSubToGroupUser = async function addSubToGroupUser (member, group) {
|
||||
{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. Tell them to cancel subscription then give them free sub.'},
|
||||
{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) return;
|
||||
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 === this.constants.GOOGLE_PAYMENT_METHOD) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE;
|
||||
} else if (memberPlan.paymentMethod === this.constants.IOS_PAYMENT_METHOD) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS;
|
||||
} else if (memberPlan.customerId === this.constants.UNLIMITED_CUSTOMER_ID) {
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_LIFETIME_FREE;
|
||||
} else if (memberPlan.customerId === this.constants.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();
|
||||
if (member.hasNotCancelled()) {
|
||||
await member.cancelSubscription({cancellationReason: JOINED_GROUP_PLAN});
|
||||
previousSubscriptionType = EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL;
|
||||
}
|
||||
|
||||
let today = new Date();
|
||||
plan = member.purchased.plan.toObject();
|
||||
@@ -164,10 +205,10 @@ api.addSubToGroupUser = async function addSubToGroupUser (member, group) {
|
||||
data.user = member;
|
||||
await this.createSubscription(data);
|
||||
|
||||
let leader = await User.findById(group.leader).exec();
|
||||
txnEmail(data.user, 'group-member-joining', [
|
||||
txnEmail(data.user, 'group-member-join', [
|
||||
{name: 'LEADER', content: leader.profile.name},
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: previousSubscriptionType},
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -409,17 +450,18 @@ api.createSubscription = async function createSubscription (data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Sets their subscription to be cancelled later
|
||||
// Cancels a subscription or group plan, setting termination to happen later
|
||||
api.cancelSubscription = async function cancelSubscription (data) {
|
||||
let plan;
|
||||
let group;
|
||||
let cancelType = 'unsubscribe';
|
||||
let groupId;
|
||||
let emailType = 'cancel-subscription';
|
||||
let emailType;
|
||||
let emailMergeData = [];
|
||||
let sendEmail = true;
|
||||
|
||||
// If we are buying a group subscription
|
||||
if (data.groupId) {
|
||||
// cancelling a group plan
|
||||
let groupFields = basicGroupFields.concat(' purchased');
|
||||
group = await Group.getGroup({user: data.user, groupId: data.groupId, populateLeader: false, groupFields});
|
||||
|
||||
@@ -438,15 +480,20 @@ api.cancelSubscription = async function cancelSubscription (data) {
|
||||
|
||||
await this.cancelGroupUsersSubscription(group);
|
||||
} else {
|
||||
// cancelling a user subscription
|
||||
plan = data.user.purchased.plan;
|
||||
emailType = 'cancel-subscription';
|
||||
// When cancelling because the user joined a group plan, no cancel-subscription email is sent
|
||||
// because the group-member-join email says the subscription is cancelled.
|
||||
if (data.cancellationReason && data.cancellationReason === JOINED_GROUP_PLAN) sendEmail = false;
|
||||
}
|
||||
|
||||
let customerId = plan.customerId;
|
||||
let now = moment();
|
||||
let defaultRemainingDays = 30;
|
||||
|
||||
if (plan.customerId === this.constants.GROUP_PLAN_CUSTOMER_ID) {
|
||||
defaultRemainingDays = 2;
|
||||
sendEmail = false; // because group-member-cancel email has already been sent
|
||||
}
|
||||
|
||||
let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days', true) : defaultRemainingDays;
|
||||
@@ -469,7 +516,7 @@ api.cancelSubscription = async function cancelSubscription (data) {
|
||||
await data.user.save();
|
||||
}
|
||||
|
||||
if (customerId !== this.constants.GROUP_PLAN_CUSTOMER_ID) txnEmail(data.user, emailType, emailMergeData);
|
||||
if (sendEmail) txnEmail(data.user, emailType, emailMergeData);
|
||||
|
||||
if (group) {
|
||||
cancelType = 'group-unsubscribe';
|
||||
|
||||
@@ -176,8 +176,18 @@ api.subscribeSuccess = async function subscribeSuccess (options = {}) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel a PayPal Subscription
|
||||
*
|
||||
* @param options
|
||||
* @param options.user The user object who is canceling
|
||||
* @param options.groupId The id of the group that is canceling
|
||||
* @param options.cancellationReason A text string to control sending an email
|
||||
*
|
||||
* @return undefined
|
||||
*/
|
||||
api.subscribeCancel = async function subscribeCancel (options = {}) {
|
||||
let {groupId, user} = options;
|
||||
let {groupId, user, cancellationReason} = options;
|
||||
|
||||
let customerId;
|
||||
if (groupId) {
|
||||
@@ -212,6 +222,7 @@ api.subscribeCancel = async function subscribeCancel (options = {}) {
|
||||
groupId,
|
||||
paymentMethod: this.constants.PAYMENT_METHOD,
|
||||
nextBill: nextBillingDate,
|
||||
cancellationReason,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -198,11 +198,12 @@ api.editSubscription = async function editSubscription (options, stripeInc) {
|
||||
* @param options
|
||||
* @param options.user The user object who is purchasing
|
||||
* @param options.groupId The id of the group purchasing a subscription
|
||||
* @param options.cancellationReason A text string to control sending an email
|
||||
*
|
||||
* @return undefined
|
||||
*/
|
||||
api.cancelSubscription = async function cancelSubscription (options, stripeInc) {
|
||||
let {groupId, user} = options;
|
||||
let {groupId, user, cancellationReason} = options;
|
||||
let customerId;
|
||||
|
||||
// @TODO: We need to mock this, but curently we don't have correct Dependency Injection. And the Stripe Api doesn't seem to be a singleton?
|
||||
@@ -252,6 +253,7 @@ api.cancelSubscription = async function cancelSubscription (options, stripeInc)
|
||||
groupId,
|
||||
nextBill,
|
||||
paymentMethod: this.constants.PAYMENT_METHOD,
|
||||
cancellationReason,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -155,21 +155,34 @@ schema.methods.addComputedStatsToJSONObj = function addComputedStatsToUserJSONOb
|
||||
return statsObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancels a subscription.
|
||||
*
|
||||
* @param options
|
||||
* @param options.user The user object who is purchasing
|
||||
* @param options.groupId The id of the group purchasing a subscription
|
||||
* @param options.headers The request headers (only for Amazon subscriptions)
|
||||
* @param options.cancellationReason A text string to control sending an email
|
||||
*
|
||||
* @return a Promise from api.cancelSubscription()
|
||||
*/
|
||||
// @TODO: There is currently a three way relation between the user, payment methods and the payment helper
|
||||
// This creates some odd Dependency Injection issues. To counter that, we use the user as the third layer
|
||||
// To negotiate between the payment providers and the payment helper (which probably has too many responsiblities)
|
||||
// In summary, currently is is best practice to use this method to cancel a user subscription, rather than calling the
|
||||
// payment helper.
|
||||
schema.methods.cancelSubscription = async function cancelSubscription () {
|
||||
schema.methods.cancelSubscription = async function cancelSubscription (options = {}) {
|
||||
let plan = this.purchased.plan;
|
||||
|
||||
options.user = this;
|
||||
if (plan.paymentMethod === amazonPayments.constants.PAYMENT_METHOD) {
|
||||
return await amazonPayments.cancelSubscription({user: this});
|
||||
return await amazonPayments.cancelSubscription(options);
|
||||
} else if (plan.paymentMethod === stripePayments.constants.PAYMENT_METHOD) {
|
||||
return await stripePayments.cancelSubscription({user: this});
|
||||
return await stripePayments.cancelSubscription(options);
|
||||
} else if (plan.paymentMethod === paypalPayments.constants.PAYMENT_METHOD) {
|
||||
return await paypalPayments.subscribeCancel({user: this});
|
||||
return await paypalPayments.subscribeCancel(options);
|
||||
}
|
||||
// Android and iOS subscriptions cannot be cancelled by Habitica.
|
||||
|
||||
return await payments.cancelSubscription({user: this});
|
||||
return await payments.cancelSubscription(options);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user