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:
parent
fabff2e7e1
commit
3a27d1bd0c
6 changed files with 77 additions and 50 deletions
|
@ -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: {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Reference in a new issue