0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-18 02:21:47 -05:00

Added support for cancellation_reason (#221)

refs https://github.com/TryGhost/Ghost/issues/12403

Adds support for sending a cancellation_reason when cancelling a plan and store the reason on the Subscription metadata
This commit is contained in:
Fabien 'egg' O'Carroll 2020-11-23 16:28:35 +00:00 committed by GitHub
parent 5879193940
commit da00444961
2 changed files with 58 additions and 38 deletions

View file

@ -91,7 +91,8 @@ module.exports = function MembersApi({
return metadata.setMetadata('stripe', data);
}
};
const stripe = paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null;
/** @type {StripePaymentProcessor} */
const stripe = (paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null);
async function ensureStripe(_req, res, next) {
if (!stripe) {
@ -261,7 +262,7 @@ module.exports = function MembersApi({
return res.end('Bad Request.');
}
// NOTE: never allow "Complimenatry" plan to be subscribed to from the client
// NOTE: never allow "Complimentary" plan to be subscribed to from the client
if (plan.toLowerCase() === 'complimentary') {
res.writeHead(400);
return res.end('Bad Request.');
@ -273,7 +274,7 @@ module.exports = function MembersApi({
email = null;
} else {
const claims = await decodeToken(identity);
email = claims.sub;
email = claims && claims.sub;
}
} catch (err) {
res.writeHead(401);
@ -317,7 +318,7 @@ module.exports = function MembersApi({
email = null;
} else {
const claims = await decodeToken(identity);
email = claims.sub;
email = claims && claims.sub;
}
} catch (err) {
res.writeHead(401);
@ -424,12 +425,33 @@ module.exports = function MembersApi({
middleware.updateSubscription.use(ensureStripe, body.json(), async function (req, res) {
const identity = req.body.identity;
const cancelAtPeriodEnd = req.body.cancel_at_period_end;
const planName = req.body.planName;
const subscriptionId = req.params.id;
const cancelAtPeriodEnd = req.body.cancel_at_period_end;
const cancellationReason = req.body.cancellation_reason;
const planName = req.body.planName;
let member;
if (cancelAtPeriodEnd === undefined && planName === undefined) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed!',
help: 'Request should contain "cancel_at_period_end" or "planName" field.'
});
}
if ((cancelAtPeriodEnd === undefined || cancelAtPeriodEnd === false) && cancellationReason !== undefined) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed!',
help: '"cancellation_reason" field requires the "cancel_at_period_end" field to be true.'
});
}
if (cancellationReason && cancellationReason.length > 500) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed!',
help: '"cancellation_reason" field can be a maximum of 500 characters.'
});
}
let email;
try {
if (!identity) {
throw new common.errors.BadRequestError({
@ -438,25 +460,21 @@ module.exports = function MembersApi({
}
const claims = await decodeToken(identity);
const email = claims.sub;
member = email ? await users.get({email}, {withRelated: ['stripeSubscriptions']}) : null;
if (!member) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed! Could not find member'
});
}
email = claims && claims.sub;
} catch (err) {
res.writeHead(401);
return res.end('Unauthorized');
}
// Don't allow removing subscriptions that don't belong to the member
const plan = planName && stripe.findPlanByNickname(planName);
if (planName && !plan) {
const member = email ? await users.get({email}, {withRelated: ['stripeSubscriptions']}) : null;
if (!member) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed! Could not find plan'
message: 'Updating subscription failed! Could not find member'
});
}
// Don't allow removing subscriptions that don't belong to the member
const subscription = member.related('stripeSubscriptions').models.find(
subscription => subscription.get('subscription_id') === subscriptionId
);
@ -465,24 +483,25 @@ module.exports = function MembersApi({
return res.end('No permission');
}
if (cancelAtPeriodEnd === undefined && planName === undefined) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed!',
help: 'Request should contain "cancel" or "plan" field.'
});
}
const subscriptionUpdate = {
id: subscription.get('subscription_id')
const subscriptionUpdateData = {
id: subscriptionId
};
if (cancelAtPeriodEnd !== undefined) {
subscriptionUpdate.cancel_at_period_end = !!(cancelAtPeriodEnd);
subscriptionUpdateData.cancel_at_period_end = cancelAtPeriodEnd;
subscriptionUpdateData.cancellation_reason = cancellationReason;
}
if (plan) {
subscriptionUpdate.plan = plan.id;
if (planName !== undefined) {
const plan = stripe.findPlanByNickname(planName);
if (!plan) {
throw new common.errors.BadRequestError({
message: 'Updating subscription failed! Could not find plan'
});
}
subscriptionUpdateData.plan = plan.id;
}
await stripe.updateSubscriptionFromClient(subscriptionUpdate);
await stripe.updateSubscriptionFromClient(subscriptionUpdateData);
res.writeHead(204);
res.end();

View file

@ -251,11 +251,12 @@ module.exports = class StripePaymentProcessor {
}
async updateSubscriptionFromClient(subscription) {
const updatedSubscription = await update(
this._stripe, 'subscriptions',
subscription.id,
_.pick(subscription, ['plan', 'cancel_at_period_end'])
);
/** @type {Object} */
const data = _.pick(subscription, ['plan', 'cancel_at_period_end']);
data.metadata = {
cancellation_reason: subscription.cancellation_reason || null
};
const updatedSubscription = await update(this._stripe, 'subscriptions', subscription.id, data);
await this._updateSubscription(updatedSubscription);
return updatedSubscription;
@ -497,7 +498,7 @@ module.exports = class StripePaymentProcessor {
return customer;
}
async getSetupIntent(id, options) {
async getSetupIntent(id, options = {}) {
return retrieve(this._stripe, 'setupIntents', id, options);
}
@ -505,7 +506,7 @@ module.exports = class StripePaymentProcessor {
return create(this._stripe, 'customers', options);
}
async getCustomer(id, options) {
async getCustomer(id, options = {}) {
return retrieve(this._stripe, 'customers', id, options);
}
};