mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-23 11:55:01 +03:00
Fixed checkout session creation for existing members (#403)
refs https://github.com/TryGhost/Team/issues/1526 We weren't using the `req.body.customerEmail` to load a member and check their existing tiers, this meant that existing members which were signed out and attempted to create a stripe checkout session were able to.
This commit is contained in:
parent
5bef2a1068
commit
8e2c600612
@ -246,30 +246,38 @@ module.exports = function MembersAPI({
|
||||
return getMemberIdentityData(email);
|
||||
}
|
||||
|
||||
const forwardError = fn => async (req, res, next) => {
|
||||
try {
|
||||
await fn(req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
const middleware = {
|
||||
sendMagicLink: Router().use(
|
||||
body.json(),
|
||||
(req, res) => routerController.sendMagicLink(req, res)
|
||||
forwardError((req, res) => routerController.sendMagicLink(req, res))
|
||||
),
|
||||
createCheckoutSession: Router().use(
|
||||
body.json(),
|
||||
(req, res) => routerController.createCheckoutSession(req, res)
|
||||
forwardError((req, res) => routerController.createCheckoutSession(req, res))
|
||||
),
|
||||
createCheckoutSetupSession: Router().use(
|
||||
body.json(),
|
||||
(req, res) => routerController.createCheckoutSetupSession(req, res)
|
||||
forwardError((req, res) => routerController.createCheckoutSetupSession(req, res))
|
||||
),
|
||||
createEvents: Router().use(
|
||||
body.json(),
|
||||
(req, res) => MembersAnalyticsIngress.createEvents(req, res)
|
||||
forwardError((req, res) => MembersAnalyticsIngress.createEvents(req, res))
|
||||
),
|
||||
updateEmailAddress: Router().use(
|
||||
body.json(),
|
||||
(req, res) => memberController.updateEmailAddress(req, res)
|
||||
forwardError((req, res) => memberController.updateEmailAddress(req, res))
|
||||
),
|
||||
updateSubscription: Router({mergeParams: true}).use(
|
||||
body.json(),
|
||||
(req, res) => memberController.updateSubscription(req, res)
|
||||
forwardError((req, res) => memberController.updateSubscription(req, res))
|
||||
),
|
||||
wellKnown: Router()
|
||||
.get('/jwks.json',
|
||||
|
@ -1,5 +1,16 @@
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const logging = require('@tryghost/logging');
|
||||
const _ = require('lodash');
|
||||
const {BadRequestError, NoPermissionError, NotFoundError, UnauthorizedError} = require('@tryghost/errors');
|
||||
|
||||
const messages = {
|
||||
badRequest: 'Bad Request.',
|
||||
notFound: 'Not Found.',
|
||||
offerArchived: 'This offer is archived.',
|
||||
tierArchived: 'This tier is archived.',
|
||||
existingSubscription: 'A subscription exists for this Member.',
|
||||
unableToCheckout: 'Unable to initiate checkout session'
|
||||
};
|
||||
|
||||
module.exports = class RouterController {
|
||||
/**
|
||||
@ -127,55 +138,56 @@ module.exports = class RouterController {
|
||||
const metadata = req.body.metadata;
|
||||
|
||||
if (!ghostPriceId && !offerId && !tierId && !cadence) {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
throw new BadRequestError({
|
||||
message: tpl(messages.badRequest)
|
||||
});
|
||||
}
|
||||
|
||||
if (offerId && (ghostPriceId || (tierId && cadence))) {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
throw new BadRequestError({
|
||||
message: tpl(messages.badRequest)
|
||||
});
|
||||
}
|
||||
|
||||
if (ghostPriceId && tierId && cadence) {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
throw new BadRequestError({
|
||||
message: tpl(messages.badRequest)
|
||||
});
|
||||
}
|
||||
|
||||
if (tierId && !cadence) {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
throw new BadRequestError({
|
||||
message: tpl(messages.badRequest)
|
||||
});
|
||||
}
|
||||
|
||||
if (cadence && cadence !== 'month' && cadence !== 'year') {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
throw new BadRequestError({
|
||||
message: tpl(messages.badRequest)
|
||||
});
|
||||
}
|
||||
|
||||
let couponId = null;
|
||||
if (offerId) {
|
||||
try {
|
||||
const offer = await this._offersAPI.getOffer({id: offerId});
|
||||
const tier = (await this._productRepository.get(offer.tier)).toJSON();
|
||||
const offer = await this._offersAPI.getOffer({id: offerId});
|
||||
const tier = (await this._productRepository.get(offer.tier)).toJSON();
|
||||
|
||||
if (offer.status === 'archived') {
|
||||
res.writeHead(403);
|
||||
return res.end('Offer is archived.');
|
||||
}
|
||||
|
||||
if (offer.cadence === 'month') {
|
||||
ghostPriceId = tier.monthly_price_id;
|
||||
} else {
|
||||
ghostPriceId = tier.yearly_price_id;
|
||||
}
|
||||
|
||||
const coupon = await this._paymentsService.getCouponForOffer(offerId);
|
||||
couponId = coupon.id;
|
||||
|
||||
metadata.offer = offer.id;
|
||||
} catch (err) {
|
||||
res.writeHead(500);
|
||||
return res.end('Could not use Offer.');
|
||||
if (offer.status === 'archived') {
|
||||
throw new NoPermissionError({
|
||||
message: tpl(messages.offerArchived)
|
||||
});
|
||||
}
|
||||
|
||||
if (offer.cadence === 'month') {
|
||||
ghostPriceId = tier.monthly_price_id;
|
||||
} else {
|
||||
ghostPriceId = tier.yearly_price_id;
|
||||
}
|
||||
|
||||
const coupon = await this._paymentsService.getCouponForOffer(offerId);
|
||||
couponId = coupon.id;
|
||||
|
||||
metadata.offer = offer.id;
|
||||
}
|
||||
|
||||
if (!ghostPriceId) {
|
||||
@ -194,8 +206,9 @@ module.exports = class RouterController {
|
||||
});
|
||||
|
||||
if (!price) {
|
||||
res.writeHead(404);
|
||||
return res.end('Not Found.');
|
||||
throw new NotFoundError({
|
||||
message: tpl(messages.notFound)
|
||||
});
|
||||
}
|
||||
|
||||
const priceId = price.get('stripe_price_id');
|
||||
@ -203,25 +216,26 @@ module.exports = class RouterController {
|
||||
const product = await this._productRepository.get({stripe_price_id: priceId});
|
||||
|
||||
if (product.get('active') !== true) {
|
||||
res.writeHead(403);
|
||||
return res.end('Tier is archived.');
|
||||
throw new NoPermissionError({
|
||||
message: tpl(messages.tierArchived)
|
||||
});
|
||||
}
|
||||
|
||||
let email;
|
||||
try {
|
||||
if (!identity) {
|
||||
email = null;
|
||||
} else {
|
||||
let member = null;
|
||||
if (identity) {
|
||||
try {
|
||||
const claims = await this._tokenService.decodeToken(identity);
|
||||
email = claims && claims.sub;
|
||||
const email = claims && claims.sub;
|
||||
if (email) {
|
||||
member = await this._memberRepository.get({email}, {withRelated: ['stripeCustomers', 'products']});
|
||||
}
|
||||
} catch (err) {
|
||||
throw new UnauthorizedError({err});
|
||||
}
|
||||
} catch (err) {
|
||||
res.writeHead(401);
|
||||
return res.end('Unauthorized');
|
||||
} else if (req.body.customerEmail) {
|
||||
member = await this._memberRepository.get({email: req.body.customerEmail}, {withRelated: ['stripeCustomers', 'products']});
|
||||
}
|
||||
|
||||
const member = email ? await this._memberRepository.get({email}, {withRelated: ['stripeCustomers', 'products']}) : null;
|
||||
|
||||
let successUrl = req.body.successUrl;
|
||||
let cancelUrl = req.body.cancelUrl;
|
||||
|
||||
@ -261,8 +275,17 @@ module.exports = class RouterController {
|
||||
}
|
||||
|
||||
if (member.related('products').length !== 0) {
|
||||
res.writeHead(403);
|
||||
return res.end('No permission');
|
||||
if (!identity && req.body.customerEmail) {
|
||||
try {
|
||||
await this._sendEmailWithMagicLink({email: req.body.customerEmail, requestedType: 'signin'});
|
||||
} catch (err) {
|
||||
logging.warn(err);
|
||||
}
|
||||
}
|
||||
throw new NoPermissionError({
|
||||
message: messages.existingSubscription,
|
||||
code: 'CANNOT_CHECKOUT_WITH_EXISTING_SUBSCRIPTION'
|
||||
});
|
||||
}
|
||||
|
||||
let stripeCustomer;
|
||||
@ -302,15 +325,16 @@ module.exports = class RouterController {
|
||||
});
|
||||
|
||||
return res.end(JSON.stringify(sessionInfo));
|
||||
} catch (e) {
|
||||
const error = e.message || 'Unable to initiate checkout session';
|
||||
res.writeHead(400);
|
||||
return res.end(error);
|
||||
} catch (err) {
|
||||
throw new BadRequestError({
|
||||
err,
|
||||
message: tpl(messages.unableToCheckout)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendMagicLink(req, res) {
|
||||
const {email, emailType, requestSrc} = req.body;
|
||||
const {email, emailType} = req.body;
|
||||
if (!email) {
|
||||
res.writeHead(400);
|
||||
return res.end('Bad Request.');
|
||||
@ -321,11 +345,11 @@ module.exports = class RouterController {
|
||||
const member = await this._memberRepository.get({email});
|
||||
if (member) {
|
||||
const tokenData = {};
|
||||
await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType, requestSrc});
|
||||
await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType});
|
||||
}
|
||||
} else {
|
||||
const tokenData = _.pick(req.body, ['labels', 'name', 'newsletters']);
|
||||
await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType, requestSrc});
|
||||
await this._sendEmailWithMagicLink({email, tokenData, requestedType: emailType});
|
||||
}
|
||||
res.writeHead(201);
|
||||
return res.end('Created.');
|
||||
|
Loading…
Reference in New Issue
Block a user