diff --git a/test/api/unit/libs/payments/stripe/calculate-termination-date.test.js b/test/api/unit/libs/payments/stripe/calculate-termination-date.test.js new file mode 100644 index 0000000000..7badd7e62a --- /dev/null +++ b/test/api/unit/libs/payments/stripe/calculate-termination-date.test.js @@ -0,0 +1,64 @@ +import moment from 'moment'; +import { calculateSubscriptionTerminationDate } from '../../../../../../website/server/libs/payments/util'; +import api from '../../../../../../website/server/libs/payments/payments'; + +describe('#calculateSubscriptionTerminationDate', () => { + let plan; + let nextBill; + + beforeEach(() => { + plan = { + customerId: 'customer-id', + extraMonths: 0, + }; + nextBill = moment(); + }); + it('should extend date to the exact amount of days left before the next bill will occur', () => { + nextBill = moment() + .add(5, 'days'); + const expectedTerminationDate = moment() + .add(5, 'days'); + + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); + it('if nextBill is null, add 30 days to termination date', () => { + nextBill = null; + const expectedTerminationDate = moment() + .add(30, 'days'); + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); + it('if nextBill is null and it\'s a group plan, add 2 days instead of 30', () => { + nextBill = null; + plan.customerId = api.constants.GROUP_PLAN_CUSTOMER_ID; + const expectedTerminationDate = moment() + .add(2, 'days'); + + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); + it('should add 30.5 days for each extraMonth', () => { + plan.extraMonths = 4; + const expectedTerminationDate = moment() + .add(30.5 * 4, 'days'); + + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); + it('should round up if total days gained by extraMonth is a decimal number', () => { + plan.extraMonths = 5; + const expectedTerminationDate = moment() + .add(Math.ceil(30.5 * 5), 'days'); + + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); + it('behaves like extraMonths is 0 if it\'s set to a negative number', () => { + plan.extraMonths = -5; + const expectedTerminationDate = moment(); + const terminationDate = calculateSubscriptionTerminationDate(nextBill, plan, api.constants); + expect(expectedTerminationDate.diff(terminationDate, 'days')).to.eql(0); + }); +}); diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index 96acf3451e..0f6d938d30 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -17,6 +17,7 @@ import { } from '../errors'; import shared from '../../../common'; import { sendNotification as sendPushNotification } from '../pushNotifications'; +import { calculateSubscriptionTerminationDate } from './util'; // @TODO: Abstract to shared/constant const JOINED_GROUP_PLAN = 'joined group plan'; @@ -309,24 +310,11 @@ async function cancelSubscription (data) { if (data.cancellationReason && data.cancellationReason === JOINED_GROUP_PLAN) sendEmail = false; } - const 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 } - const remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days', true) : defaultRemainingDays; - if (plan.extraMonths < 0) plan.extraMonths = 0; - const extraDays = Math.ceil(30.5 * plan.extraMonths); - const nowStr = `${now.format('MM')}/${now.format('DD')}/${now.format('YYYY')}`; - const nowStrFormat = 'MM/DD/YYYY'; - - plan.dateTerminated = moment(nowStr, nowStrFormat) - .add({ days: remaining }) - .add({ days: extraDays }) - .toDate(); + plan.dateTerminated = calculateSubscriptionTerminationDate(data.nextBill, plan, this.constants); // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated plan.extraMonths = 0; diff --git a/website/server/libs/payments/util.js b/website/server/libs/payments/util.js new file mode 100644 index 0000000000..fb6b65daaa --- /dev/null +++ b/website/server/libs/payments/util.js @@ -0,0 +1,32 @@ +import moment from 'moment'; + +const DEFAULT_REMAINING_DAYS = 30; +const DEFAULT_REMAINING_DAYS_FOR_GROUP_PLAN = 2; + +/** + * paymentsApiConstants is provided as parameter because of a dependency cycle + * with subscriptions api which will occur if api.constants would be used directly + */ +export function calculateSubscriptionTerminationDate ( + nextBill, purchasedPlan, paymentsApiConstants, +) { + const defaultRemainingDays = ( + purchasedPlan.customerId === paymentsApiConstants.GROUP_PLAN_CUSTOMER_ID + ) ? DEFAULT_REMAINING_DAYS_FOR_GROUP_PLAN + : DEFAULT_REMAINING_DAYS; + const now = moment(); + + const remaining = nextBill + ? moment(nextBill).diff(new Date(), 'days', true) + : defaultRemainingDays; + + const extraMonths = Math.max(purchasedPlan.extraMonths, 0); + const extraDays = Math.ceil(30.5 * extraMonths); + const nowStr = `${now.format('MM')}/${now.format('DD')}/${now.format('YYYY')}`; + const nowStrFormat = 'MM/DD/YYYY'; + + return moment(nowStr, nowStrFormat) + .add({ days: remaining }) + .add({ days: extraDays }) + .toDate(); +}