🐛 Fixed checkout sessions when using Offers

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

The issue here is two-fold, and specific to using Offers so was not
caught by any automated tests. First, we were incorrectly comparing
the tier.id to the offer.tier.id - this is because the Tier objects id
property is an instance of ObjectID rather than a string.

Secondly we were passing through the cadence parameter from the
request body, but when using Offers this is not including in the
request, so we must pull the data off of the Offer object instead and
pass that to the payments service.
This commit is contained in:
Fabien "egg" O'Carroll 2022-11-01 21:47:49 +07:00 committed by Fabien 'egg' O'Carroll
parent 3c71d07dfb
commit 1f300fb781
4 changed files with 91 additions and 2 deletions

View File

@ -1,5 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Create Stripe Checkout Session Can create a checkout session when using offers 1: [body] 1`] = `
Object {
"url": "https://site.com",
}
`;
exports[`Create Stripe Checkout Session Can create a checkout session when using offers 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

@ -55,6 +55,78 @@ describe('Create Stripe Checkout Session', function () {
});
});
it('Can create a checkout session when using offers', async function () {
const {body: {tiers}} = await adminAgent.get('/tiers/?include=monthly_price&yearly_price');
const paidTier = tiers.find(tier => tier.type === 'paid');
const {body: {offers: [offer]}} = await adminAgent.post('/offers/').body({
offers: [{
name: 'Test Offer',
code: 'test-offer',
cadence: 'month',
status: 'active',
currency: 'usd',
type: 'percent',
amount: 20,
duration: 'once',
duration_in_months: null,
display_title: 'Test Offer',
display_description: null,
tier: {
id: paidTier.id
}
}]
});
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') {
return [200, {id: 'cs_123', url: 'https://site.com'}];
}
if (uri === '/v1/coupons') {
return [200, {id: 'coupon_123', url: 'https://site.com'}];
}
return [500];
});
await membersAgent.post('/api/create-stripe-checkout-session/')
.body({
customerEmail: 'free@test.com',
offerId: offer.id
})
.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

@ -142,7 +142,7 @@ module.exports = class RouterController {
async createCheckoutSession(req, res) {
let ghostPriceId = req.body.priceId;
const tierId = req.body.tierId;
const cadence = req.body.cadence;
let cadence = req.body.cadence;
const identity = req.body.identity;
const offerId = req.body.offerId;
const metadata = req.body.metadata ?? {};
@ -185,6 +185,7 @@ module.exports = class RouterController {
if (offerId) {
offer = await this._offersAPI.getOffer({id: offerId});
tier = await this._tiersService.api.read(offer.tier.id);
cadence = offer.cadence;
} else {
offer = null;
tier = await this._tiersService.api.read(tierId);

View File

@ -67,7 +67,7 @@ class PaymentsService {
let coupon = null;
let trialDays = null;
if (offer) {
if (offer.tier.id !== tier.id) {
if (!tier.id.equals(offer.tier.id)) {
throw new BadRequestError({
message: 'This Offer is not valid for the Tier'
});