diff --git a/apps/portal/src/utils/helpers.js b/apps/portal/src/utils/helpers.js index ad60585ed5..7135f3c41e 100644 --- a/apps/portal/src/utils/helpers.js +++ b/apps/portal/src/utils/helpers.js @@ -556,15 +556,7 @@ export function subscriptionHasFreeTrial({sub} = {}) { } export function isInThePast(date) { - const today = new Date(); - - // 👇️ OPTIONAL! - // This line sets the hour of the current date to midnight - // so the comparison only returns `true` if the passed in date - // is at least yesterday - today.setHours(0, 0, 0, 0); - - return date < today; + return date < new Date(); } export function getProductFromPrice({site, priceId}) { diff --git a/apps/portal/src/utils/helpers.test.js b/apps/portal/src/utils/helpers.test.js index 85cec013fe..adea069cdb 100644 --- a/apps/portal/src/utils/helpers.test.js +++ b/apps/portal/src/utils/helpers.test.js @@ -1,4 +1,4 @@ -import {getAllProductsForSite, getAvailableProducts, getCurrencySymbol, getFreeProduct, getMemberName, getMemberSubscription, getPriceFromSubscription, getPriceIdFromPageQuery, getSupportAddress, getUrlHistory, hasMultipleProducts, isActiveOffer, isInviteOnlySite, isPaidMember, isSameCurrency, transformApiTiersData, isSigninAllowed, isSignupAllowed, getCompExpiry} from './helpers'; +import {getAllProductsForSite, getAvailableProducts, getCurrencySymbol, getFreeProduct, getMemberName, getMemberSubscription, getPriceFromSubscription, getPriceIdFromPageQuery, getSupportAddress, getUrlHistory, hasMultipleProducts, isActiveOffer, isInviteOnlySite, isPaidMember, isSameCurrency, transformApiTiersData, isSigninAllowed, isSignupAllowed, getCompExpiry, isInThePast} from './helpers'; import * as Fixtures from './fixtures-generator'; import {site as FixturesSite, member as FixtureMember, offer as FixtureOffer, transformTierFixture as TransformFixtureTiers} from '../utils/test-fixtures'; import {isComplimentaryMember} from '../utils/helpers'; @@ -448,4 +448,17 @@ describe('Helpers - ', () => { expect(getCompExpiry({member})).toEqual(''); }); }); + + describe('isInThePast', () => { + it('returns a boolean indicating if the provided date is in the past', () => { + const pastDate = new Date(); + pastDate.setDate(pastDate.getDate() - 1); + + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 1); + + expect(isInThePast(pastDate)).toEqual(true); + expect(isInThePast(futureDate)).toEqual(false); + }); + }); }); diff --git a/ghost/members-api/lib/repositories/MemberRepository.js b/ghost/members-api/lib/repositories/MemberRepository.js index aa8d90797e..e79f1a3604 100644 --- a/ghost/members-api/lib/repositories/MemberRepository.js +++ b/ghost/members-api/lib/repositories/MemberRepository.js @@ -21,6 +21,8 @@ const messages = { invalidEmail: 'Invalid Email' }; +const SUBSCRIPTION_STATUS_TRIALING = 'trialing'; + /** * @typedef {object} ITokenService * @prop {(token: string) => Promise} decodeToken @@ -1367,6 +1369,10 @@ module.exports = class MemberRepository { data.subscription.price ); updatedSubscription = await this._stripeAPIService.removeCouponFromSubscription(subscription.id); + + if (subscriptionModel.get('status') === SUBSCRIPTION_STATUS_TRIALING) { + updatedSubscription = await this._stripeAPIService.cancelSubscriptionTrial(subscription.id); + } } } diff --git a/ghost/stripe/lib/StripeAPI.js b/ghost/stripe/lib/StripeAPI.js index b03a257c4c..8e0c58bcbd 100644 --- a/ghost/stripe/lib/StripeAPI.js +++ b/ghost/stripe/lib/StripeAPI.js @@ -759,4 +759,16 @@ module.exports = class StripeAPI { default_payment_method: paymentMethod }); } + + /** + * @param {string} id - The ID of the subscription to cancel the trial for + * + * @returns {Promise} + */ + async cancelSubscriptionTrial(id) { + await this._rateLimitBucket.throttle(); + return this._stripe.subscriptions.update(id, { + trial_end: 'now' + }); + } }; diff --git a/ghost/stripe/test/unit/lib/StripeAPI.test.js b/ghost/stripe/test/unit/lib/StripeAPI.test.js index fb4e04c990..3382fd2814 100644 --- a/ghost/stripe/test/unit/lib/StripeAPI.test.js +++ b/ghost/stripe/test/unit/lib/StripeAPI.test.js @@ -250,4 +250,37 @@ describe('StripeAPI', function () { }); }); }); + + describe('cancelSubscriptionTrial', function () { + const mockSubscription = { + id: 'sub_123' + }; + beforeEach(function () { + mockStripe = { + subscriptions: { + update: sinon.stub().resolves(mockSubscription) + } + }; + const mockStripeConstructor = sinon.stub().returns(mockStripe); + StripeAPI.__set__('Stripe', mockStripeConstructor); + api.configure({ + secretKey: '' + }); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('cancels a subscription trial', async function () { + const result = await api.cancelSubscriptionTrial(mockSubscription.id); + + should.equal(mockStripe.subscriptions.update.callCount, 1); + + should.equal(mockStripe.subscriptions.update.args[0][0], mockSubscription.id); + should.deepEqual(mockStripe.subscriptions.update.args[0][1], {trial_end: 'now'}); + + should.deepEqual(result, mockSubscription); + }); + }); });