diff --git a/ghost/stripe/lib/StripeAPI.js b/ghost/stripe/lib/StripeAPI.js index b5ae3be4ed..3a0b31f64f 100644 --- a/ghost/stripe/lib/StripeAPI.js +++ b/ghost/stripe/lib/StripeAPI.js @@ -364,7 +364,9 @@ module.exports = class StripeAPI { */ async createCheckoutSession(priceId, customer, options) { const metadata = options.metadata || undefined; + const customerId = customer ? customer.id : undefined; const customerEmail = customer ? customer.email : options.customerEmail; + await this._rateLimitBucket.throttle(); let discounts; if (options.coupon) { @@ -386,11 +388,11 @@ module.exports = class StripeAPI { delete subscriptionData.trial_from_plan; subscriptionData.trial_period_days = options.trialDays; } - const session = await this._stripe.checkout.sessions.create({ + + let stripeSessionOptions = { payment_method_types: ['card'], success_url: options.successUrl || this._config.checkoutSessionSuccessUrl, cancel_url: options.cancelUrl || this._config.checkoutSessionCancelUrl, - customer_email: customerEmail, // @ts-ignore - we need to update to latest stripe library to correctly use newer features allow_promotion_codes: discounts ? undefined : this._config.enablePromoCodes, metadata, @@ -405,7 +407,17 @@ module.exports = class StripeAPI { // however, this would lose the "trial from plan" feature which has also // been deprecated by Stripe subscription_data: subscriptionData - }); + }; + + /* We are only allowed to specify one of these; email will be pulled from + customer object on Stripe side if that object already exists. */ + if (customerId) { + stripeSessionOptions.customer = customerId; + } else { + stripeSessionOptions.customer_email = customerEmail; + } + + const session = await this._stripe.checkout.sessions.create(stripeSessionOptions); return session; } diff --git a/ghost/stripe/test/unit/lib/StripeAPI.test.js b/ghost/stripe/test/unit/lib/StripeAPI.test.js index 76bd89de91..f834b67c36 100644 --- a/ghost/stripe/test/unit/lib/StripeAPI.test.js +++ b/ghost/stripe/test/unit/lib/StripeAPI.test.js @@ -80,4 +80,59 @@ describe('StripeAPI', function () { should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.subscription_data.trial_from_plan, true); should.not.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.subscription_data.trial_period_days); }); + + it('createCheckoutSession passes customer ID successfully to Stripe', async function (){ + const mockCustomer = { + id: 'cust_mock_123456', + customer_email: 'foo@example.com', + name: 'Example Customer' + }; + + await api.createCheckoutSession('priceId', mockCustomer, { + trialDays: null + }); + + should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer); + should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer, 'cust_mock_123456'); + }); + + it('createCheckoutSession passes email if no customer object provided', async function (){ + await api.createCheckoutSession('priceId', undefined, { + customerEmail: 'foo@example.com', + trialDays: null + }); + + should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email); + should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email, 'foo@example.com'); + }); + + it('createCheckoutSession passes email if customer object provided w/o ID', async function (){ + const mockCustomer = { + email: 'foo@example.com', + name: 'Example Customer' + }; + + await api.createCheckoutSession('priceId', mockCustomer, { + trialDays: null + }); + + should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email); + should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email, 'foo@example.com'); + }); + + it('createCheckoutSession passes only one of customer ID and email', async function (){ + const mockCustomer = { + id: 'cust_mock_123456', + email: 'foo@example.com', + name: 'Example Customer' + }; + + await api.createCheckoutSession('priceId', mockCustomer, { + trialDays: null + }); + + should.not.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email); + should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer); + should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer, 'cust_mock_123456'); + }); });