🐛 Fixed upgrading to a paid plan

closes https://github.com/TryGhost/Team/issues/2196

We were incorrectly assuming that all requests would have the
`customerEmail` passed in the body. Instead we were incorrectly
passing `undefined` or `''` as the `customerEmail` property to stripe,
which resulted in a validation error.

We've updated the code to pass `null` in the case of a falsy value,
which the Stripe API handles without error.
This commit is contained in:
Fabien "egg" O'Carroll 2022-11-01 21:48:43 +07:00 committed by Fabien 'egg' O'Carroll
parent 1f300fb781
commit ba41f308c7
3 changed files with 77 additions and 3 deletions

View File

@ -16,6 +16,22 @@ Object {
}
`;
exports[`Create Stripe Checkout Session Can create a checkout session without passing a customerEmail 1: [body] 1`] = `
Object {
"url": "https://site.com",
}
`;
exports[`Create Stripe Checkout Session Can create a checkout session without passing a customerEmail 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-type": "application/json",
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Create Stripe Checkout Session Does allow to create a checkout session if the customerEmail is not associated with a paid member 1: [body] 1`] = `
Object {
"url": "https://site.com",

View File

@ -1,3 +1,4 @@
const querystring = require('querystring');
const {agentProvider, mockManager, fixtureManager, matchers} = require('../../utils/e2e-framework');
const nock = require('nock');
const should = require('should');
@ -127,6 +128,61 @@ describe('Create Stripe Checkout Session', function () {
.matchHeaderSnapshot();
});
it('Can create a checkout session without passing a customerEmail', async function () {
const {body: {tiers}} = await adminAgent.get('/tiers/?include=monthly_price&yearly_price');
const paidTier = tiers.find(tier => tier.type === 'paid');
nock('https://api.stripe.com')
.persist()
.get(/v1\/.*/)
.reply((uri, body) => {
const [match, resource, id] = uri.match(/\/v1\/(\w+)\/(.+)\/?/) || [null];
if (match) {
if (resource === 'products') {
return [200, {
id: id,
active: true
}];
}
if (resource === 'prices') {
return [200, {
id: id,
active: true,
currency: 'usd',
unit_amount: 500
}];
}
}
return [500];
});
nock('https://api.stripe.com')
.persist()
.post(/v1\/.*/)
.reply((uri, body) => {
if (uri === '/v1/checkout/sessions') {
const bodyJSON = querystring.parse(body);
// TODO: Actually work out what Stripe checks and when/how it errors
if (bodyJSON.customerEmail) {
return [400, {error: 'Invalid Email'}];
}
return [200, {id: 'cs_123', url: 'https://site.com'}];
}
return [500];
});
await membersAgent.post('/api/create-stripe-checkout-session/')
.body({
tierId: paidTier.id,
cadence: 'month'
})
.expectStatus(200)
.matchBodySnapshot()
.matchHeaderSnapshot();
});
it('Does allow to create a checkout session if the customerEmail is not associated with a paid member', async function () {
const {body: {tiers}} = await adminAgent.get('/tiers/?include=monthly_price&yearly_price');

View File

@ -86,11 +86,13 @@ class PaymentsService {
const price = await this.getPriceForTierCadence(tier, cadence);
const email = options.email || null;
const session = await this.stripeAPIService.createCheckoutSession(price.id, customer, {
metadata,
successUrl: options.successUrl,
cancelUrl: options.cancelUrl,
customerEmail: options.email,
customerEmail: customer ? email : null,
trialDays: trialDays ?? tier.trialDays,
coupon: coupon?.id
});
@ -119,8 +121,8 @@ class PaymentsService {
async createCustomerForMember(member) {
const customer = await this.stripeAPIService.createCustomer({
email: member.email,
name: member.name
email: member.get('email'),
name: member.get('name')
});
await this.StripeCustomerModel.add({