From 098f40bbe3cc144613791fb88d4151c33579e31f Mon Sep 17 00:00:00 2001 From: Rishabh Garg Date: Tue, 9 Aug 2022 17:28:00 +0530 Subject: [PATCH] Added trial info to member subscription detail (#15193) refs https://github.com/TryGhost/Team/issues/1757 - exposes trial start and end dates in member's subscription object - allows portal and admin to show member's trial information in UI --- .../models/stripe-customer-subscription.js | 6 ++ .../members-newsletters.test.js.snap | 4 +- .../admin/__snapshots__/members.test.js.snap | 26 +++--- .../stripe-customer-subscription.test.js | 83 +++++++++++++++++++ 4 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 ghost/core/test/unit/server/models/stripe-customer-subscription.test.js diff --git a/ghost/core/core/server/models/stripe-customer-subscription.js b/ghost/core/core/server/models/stripe-customer-subscription.js index 598b8c1469..b36a7dacda 100644 --- a/ghost/core/core/server/models/stripe-customer-subscription.js +++ b/ghost/core/core/server/models/stripe-customer-subscription.js @@ -1,5 +1,6 @@ const ghostBookshelf = require('./base'); const _ = require('lodash'); +const labs = require('../../shared/labs'); const StripeCustomerSubscription = ghostBookshelf.Model.extend({ tableName: 'members_stripe_customers_subscriptions', @@ -42,6 +43,11 @@ const StripeCustomerSubscription = ghostBookshelf.Model.extend({ current_period_end: defaultSerializedObject.current_period_end }; + if (labs.isSet('freeTrial')) { + serialized.trial_start_at = defaultSerializedObject.trial_start_at; + serialized.trial_end_at = defaultSerializedObject.trial_end_at; + } + if (!_.isEmpty(defaultSerializedObject.stripePrice)) { serialized.price = { id: defaultSerializedObject.stripePrice.stripe_price_id, diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/members-newsletters.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/members-newsletters.test.js.snap index f84c35b7cf..66730c53ff 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/members-newsletters.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/members-newsletters.test.js.snap @@ -218,7 +218,7 @@ exports[`Members API - With Newsletters - compat mode Can fetch members who are Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "11520", + "content-length": "11646", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -444,7 +444,7 @@ exports[`Members API - With Newsletters Can fetch members who are subscribed 2: Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "11520", + "content-length": "11646", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap index 6739a10c9f..abc72a5098 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap @@ -386,6 +386,8 @@ Object { }, "start_date": Any, "status": "active", + "trial_end_at": null, + "trial_start_at": null, }, ], "tiers": Array [], @@ -400,7 +402,7 @@ exports[`Members API Can add a subscription 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2097", + "content-length": "2139", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -464,6 +466,8 @@ Object { }, "start_date": Any, "status": "active", + "trial_end_at": null, + "trial_start_at": null, }, ], "tiers": Array [], @@ -478,7 +482,7 @@ exports[`Members API Can add a subscription 4: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2097", + "content-length": "2139", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -924,7 +928,7 @@ exports[`Members API Can add complimentary subscription (out of date) 4: [header Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2541", + "content-length": "2583", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1121,7 +1125,7 @@ exports[`Members API Can browse 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "13490", + "content-length": "13616", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1305,7 +1309,7 @@ exports[`Members API Can create a member with an existing complimentary subscrip Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2592", + "content-length": "2634", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1374,7 +1378,7 @@ exports[`Members API Can create a member with an existing paid subscription 2: [ Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2578", + "content-length": "2620", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//, @@ -1822,7 +1826,7 @@ exports[`Members API Can filter by paid status 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "9809", + "content-length": "9935", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -1935,7 +1939,7 @@ exports[`Members API Can filter on newsletter slug 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "8558", + "content-length": "8642", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2140,7 +2144,7 @@ exports[`Members API Can filter on tier slug 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "20485", + "content-length": "20695", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -2324,7 +2328,7 @@ exports[`Members API Can ignore any unknown includes 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "9809", + "content-length": "9935", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", @@ -3490,7 +3494,7 @@ exports[`Members API Search for paid members retrieves member with email paid@te Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "2444", + "content-length": "2486", "content-type": "application/json; charset=utf-8", "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, "vary": "Origin, Accept-Encoding", diff --git a/ghost/core/test/unit/server/models/stripe-customer-subscription.test.js b/ghost/core/test/unit/server/models/stripe-customer-subscription.test.js new file mode 100644 index 0000000000..b975de853f --- /dev/null +++ b/ghost/core/test/unit/server/models/stripe-customer-subscription.test.js @@ -0,0 +1,83 @@ +const sinon = require('sinon'); +const should = require('should'); +const models = require('../../../../core/server/models'); +const labs = require('../../../../core/shared/labs'); + +describe('Unit: models/stripe-customer-subscription', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('serialize', function () { + let serialize; + + beforeEach(function () { + serialize = function (model, options) { + return new models.StripeCustomerSubscription(model).serialize(options); + }; + }); + + afterEach(function () { + sinon.restore(); + }); + + it('ignores trial details when flag is off', function () { + const trialStartAt = new Date(); + const trialEndAt = new Date(Date.now() + 7 * 86400 * 1000); // trial ending 7 days from now + const stripeSubscription = { + customer_id: 'fake_customer_id', + subscription_id: 'fake_subscription_id', + plan_id: 'fake_plan_id', + stripe_price_id: 'fake_plan_id', + plan_amount: 1337, + plan_nickname: 'e-LEET', + plan_interval: 'year', + plan_currency: 'btc', + status: 'active', + start_date: new Date(), + current_period_end: new Date(), + cancel_at_period_end: false, + trial_start_at: trialStartAt, + trial_end_at: trialEndAt + }; + + const json = serialize(stripeSubscription); + should.exist(json.plan); + should.exist(json.customer); + should.not.exist(json.trial_start_at); + should.not.exist(json.trial_end_at); + }); + + it('returns trial details with flag enabled', function () { + sinon.stub(labs, 'isSet').returns(true); + const trialStartAt = new Date(); + const trialEndAt = new Date(Date.now() + 7 * 86400 * 1000); // trial ending 7 days from now + const stripeSubscription = { + customer_id: 'fake_customer_id', + subscription_id: 'fake_subscription_id', + plan_id: 'fake_plan_id', + stripe_price_id: 'fake_plan_id', + plan_amount: 1337, + plan_nickname: 'e-LEET', + plan_interval: 'year', + plan_currency: 'btc', + status: 'active', + start_date: new Date(), + current_period_end: new Date(), + cancel_at_period_end: false, + trial_start_at: trialStartAt, + trial_end_at: trialEndAt + }; + + const json = serialize(stripeSubscription); + should.exist(json.plan); + should.exist(json.customer); + should.exist(json.trial_start_at); + should.exist(json.trial_end_at); + }); + }); +});