0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Updated APIs to use price ids

refs https://github.com/TryGhost/Team/issues/637

All the APIs that currently work with price names needs to be updated to work with price ids instead to work with custom prices/products. This change updates APIs to work with Price IDs in `checkout` , `updateSubscription` and other APIs/methods.
This commit is contained in:
Rishabh 2021-05-04 21:27:58 +05:30 committed by Rishabh Garg
parent fabff2e7e1
commit 3a27d1bd0c
6 changed files with 77 additions and 50 deletions

View file

@ -46,7 +46,8 @@ module.exports = function MembersApi({
MemberEmailChangeEvent,
StripeProduct,
StripePrice,
Product
Product,
Settings
},
logger
}) {
@ -72,6 +73,7 @@ module.exports = function MembersApi({
StripeProduct,
StripePrice,
Product,
Settings,
logger
});
@ -106,8 +108,7 @@ module.exports = function MembersApi({
MemberPaidSubscriptionEvent,
MemberPaymentEvent,
MemberStatusEvent,
MemberLoginEvent,
MemberEmailChangeEvent
MemberLoginEvent
});
const stripeWebhookService = new StripeWebhookService({
@ -138,17 +139,17 @@ module.exports = function MembersApi({
const memberController = new MemberController({
memberRepository,
StripePrice,
stripeAPIService,
stripePlansService,
tokenService
});
const routerController = new RouterController({
memberRepository,
StripePrice,
allowSelfSignup,
magicLinkService,
stripeAPIService,
stripePlansService,
tokenService,
sendEmailWithMagicLink,
config: {

View file

@ -5,17 +5,20 @@ const errors = require('ghost-ignition').errors;
*
* @param {object} deps
* @param {any} deps.memberRepository
* @param {any} deps.stripePlansService
* @param {any} deps.StripePrice
* @param {any} deps.stripeApiService
* @param {any} deps.tokenService
*/
module.exports = class MemberController {
constructor({
memberRepository,
stripePlansService,
StripePrice,
stripeAPIService,
tokenService
}) {
this._memberRepository = memberRepository;
this._stripePlansService = stripePlansService;
this._StripePrice = StripePrice;
this._stripeApiService = stripeAPIService;
this._tokenService = tokenService;
}
@ -26,12 +29,11 @@ module.exports = class MemberController {
const cancelAtPeriodEnd = req.body.cancel_at_period_end;
const smartCancel = req.body.smart_cancel;
const cancellationReason = req.body.cancellation_reason;
const planName = req.body.planName;
if (cancelAtPeriodEnd === undefined && planName === undefined && smartCancel === undefined) {
const ghostPriceId = req.body.priceId;
if (cancelAtPeriodEnd === undefined && ghostPriceId === undefined && smartCancel === undefined) {
throw new errors.BadRequestError({
message: 'Updating subscription failed!',
help: 'Request should contain "cancel_at_period_end" or "planName" or "smart_cancel" field.'
help: 'Request should contain "cancel_at_period_end" or "priceId" or "smart_cancel" field.'
});
}
@ -70,18 +72,22 @@ module.exports = class MemberController {
});
}
if (planName !== undefined) {
const plan = this._stripePlansService.getPlan(planName);
if (!plan) {
throw new errors.BadRequestError({
message: 'Updating subscription failed! Could not find plan'
});
if (ghostPriceId !== undefined) {
const price = await this._StripePrice.findOne({
id: ghostPriceId
});
if (!price) {
res.writeHead(404);
return res.end('Not Found.');
}
const priceId = price.get('stripe_price_id');
await this._memberRepository.updateSubscription({
email,
subscription: {
subscription_id: subscriptionId,
plan: plan.id
price: priceId
}
});
} else if (cancelAtPeriodEnd !== undefined) {

View file

@ -7,29 +7,29 @@ const errors = require('ghost-ignition').errors;
*
* @param {object} deps
* @param {any} deps.memberRepository
* @param {any} deps.StripePrice
* @param {boolean} deps.allowSelfSignup
* @param {any} deps.magicLinkService
* @param {any} deps.stripeAPIService
* @param {any} deps.stripePlanService
* @param {any} deps.tokenService
* @param {any} deps.config
*/
module.exports = class RouterController {
constructor({
memberRepository,
StripePrice,
allowSelfSignup,
magicLinkService,
stripeAPIService,
stripePlansService,
tokenService,
sendEmailWithMagicLink,
config
}) {
this._memberRepository = memberRepository;
this._StripePrice = StripePrice;
this._allowSelfSignup = allowSelfSignup;
this._magicLinkService = magicLinkService;
this._stripeAPIService = stripeAPIService;
this._stripePlansService = stripePlansService;
this._tokenService = tokenService;
this._sendEmailWithMagicLink = sendEmailWithMagicLink;
this._config = config;
@ -111,21 +111,24 @@ module.exports = class RouterController {
}
async createCheckoutSession(req, res) {
const planName = req.body.plan;
const ghostPriceId = req.body.priceId;
const identity = req.body.identity;
if (!planName) {
if (!ghostPriceId) {
res.writeHead(400);
return res.end('Bad Request.');
}
// NOTE: never allow "Complimentary" plan to be subscribed to from the client
if (planName.toLowerCase() === 'complimentary') {
res.writeHead(400);
return res.end('Bad Request.');
const price = await this._StripePrice.findOne({
id: ghostPriceId
});
if (!price) {
res.writeHead(404);
return res.end('Not Found.');
}
const plan = this._stripePlansService.getPlan(planName);
const priceId = price.get('stripe_price_id');
let email;
try {
@ -144,7 +147,7 @@ module.exports = class RouterController {
if (!member) {
const customer = null;
const session = await this._stripeAPIService.createCheckoutSession(plan, customer, {
const session = await this._stripeAPIService.createCheckoutSession(priceId, customer, {
successUrl: req.body.successUrl || this._config.checkoutSuccessUrl,
cancelUrl: req.body.cancelUrl || this._config.checkoutCancelUrl,
customerEmail: req.body.customerEmail,
@ -190,7 +193,7 @@ module.exports = class RouterController {
}
try {
const session = await this._stripeAPIService.createCheckoutSession(plan, stripeCustomer, {
const session = await this._stripeAPIService.createCheckoutSession(priceId, stripeCustomer, {
successUrl: req.body.successUrl || this._config.checkoutSuccessUrl,
cancelUrl: req.body.cancelUrl || this._config.checkoutCancelUrl,
metadata: req.body.metadata

View file

@ -511,21 +511,28 @@ module.exports = class MemberRepository {
const member = await this._Member.findOne(findQuery);
const subscription = await member.related('stripeSubscriptions').query({
const subscriptionModel = await member.related('stripeSubscriptions').query({
where: {
subscription_id: data.subscription.subscription_id
}
}).fetchOne(options);
if (!subscription) {
if (!subscriptionModel) {
throw new Error('Subscription not found');
}
let updatedSubscription;
if (data.subscription.plan) {
updatedSubscription = await this._stripeAPIService.changeSubscriptionPlan(
data.subscription.subscription_id,
data.subscription.plan
if (data.subscription.price) {
const subscription = await this._stripeAPIService.getSubscription(
data.subscription.subscription_id
);
const subscriptionItem = subscription.items.data[0];
updatedSubscription = await this._stripeAPIService.updateSubscriptionItemPrice(
subscription.id,
subscriptionItem.id,
data.subscription.price
);
}

View file

@ -383,13 +383,13 @@ module.exports = class StripeAPIService {
}
/**
* @param {IPlan} plan
* @param {string} priceId
* @param {ICustomer} customer
* @param {object} options
*
* @returns {Promise<import('stripe').Stripe.Checkout.Session>}
*/
async createCheckoutSession(plan, customer, options) {
async createCheckoutSession(priceId, customer, options) {
const metadata = options.metadata || undefined;
const customerEmail = customer ? customer.email : options.customerEmail;
await this._rateLimitBucket.throttle();
@ -404,7 +404,7 @@ module.exports = class StripeAPIService {
subscription_data: {
trial_from_plan: true,
items: [{
plan: plan.id
plan: priceId
}]
}
});
@ -543,15 +543,19 @@ module.exports = class StripeAPIService {
}
/**
* @param {string} id - The ID of the Subscription to modify
* @param {string} plan - The ID of the new Plan
* @param {string} subscriptionId - The ID of the Subscription to modify
* @param {string} id - The ID of the SubscriptionItem
* @param {string} price - The ID of the new Price
*
* @returns {Promise<import('stripe').Stripe.Subscription>}
*/
async changeSubscriptionPlan(id, plan) {
async updateSubscriptionItemPrice(subscriptionId, id, price) {
await this._rateLimitBucket.throttle();
const subscription = await this._stripe.subscriptions.update(id, {
plan,
const subscription = await this._stripe.subscriptions.update(subscriptionId, {
items: [{
id,
price
}],
cancel_at_period_end: false,
metadata: {
cancellation_reason: null

View file

@ -7,8 +7,14 @@ describe('MemberController', function () {
const tokenService = {
decodeToken: sinon.fake.resolves({sub: 'fake@email.com'})
};
const stripePlansService = {
getPlan: sinon.fake.returns({id: 'plan_id'})
const StripePrice = {
findOne: sinon.fake.returns({
id: 'plan_id',
stripe_price_id: 'stripe_price_id',
get: () => {
return 'stripe_price_id';
}
})
};
const memberRepository = {
@ -16,21 +22,21 @@ describe('MemberController', function () {
email: 'fake@email.com',
subscription: {
subscription_id: 'subscription_id',
plan: 'plan_id'
price: 'stripe_price_id'
}
})
};
const controller = new MemberController({
memberRepository,
stripePlansService,
StripePrice,
tokenService
});
const req = {
body: {
identity: 'token',
planName: 'plan_name'
priceId: 'plan_name'
},
params: {
id: 'subscription_id'