Skip to content
This repository has been archived by the owner on Oct 5, 2022. It is now read-only.

Added subscription update middleware #107

Merged
merged 16 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion packages/members-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ module.exports = function MembersApi({
const middleware = {
sendMagicLink: Router(),
createCheckoutSession: Router(),
handleStripeWebhook: Router()
handleStripeWebhook: Router(),
updateSubscription: Router({mergeParams: true})
};

middleware.sendMagicLink.use(body.json(), async function (req, res) {
Expand Down Expand Up @@ -231,6 +232,57 @@ 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 subscriptionId = req.params.id;
allouis marked this conversation as resolved.
Show resolved Hide resolved

let member;

try {
if (!identity) {
naz marked this conversation as resolved.
Show resolved Hide resolved
throw new common.errors.BadRequestError({
message: 'Cancel membership failed! Could not find member'
});
}

const claims = await decodeToken(identity);
const email = claims.sub;
member = email ? await users.get({email}) : null;

if (!member) {
throw new common.errors.BadRequestError({
message: 'Cancel membership failed! Could not find member'
});
}
} catch (err) {
res.writeHead(401);
return res.end('Unauthorized');
}

// Don't allow removing subscriptions that don't belong to the member
const subscription = member.stripe.subscriptions.find(sub => sub.id === subscriptionId);

if (!subscription) {
res.writeHead(403);
return res.end('No permission');
}

if (cancelAtPeriodEnd === undefined) {
throw new common.errors.BadRequestError({
message: 'Canceling membership failed!',
help: 'Request should contain boolean "cancel" field.'
});
}

subscription.cancel_at_period_end = !!(cancelAtPeriodEnd);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allouis tried to be malicious about what we pass along to stripe and sent different types of data through this parameter. To avoid unnecessary complexity with error handling and type checking went with coercion solution. Let me know if you'd rather go with proper type checking here and maybe throw the same way Stripe API does?


await stripe.updateSubscriptionFromClient(subscription);

res.writeHead(204);
res.end();
});

const getPublicConfig = function () {
return Promise.resolve({
publicKey,
Expand Down
4 changes: 4 additions & 0 deletions packages/members-api/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ module.exports = {
Object.assign(loggerInterface, Object.create(newLogger));
}
});
},

get errors() {
return require('ghost-ignition').errors;
}
};
13 changes: 12 additions & 1 deletion packages/members-api/lib/stripe/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const debug = require('ghost-ignition').debug('stripe');
const {retrieve, list, create, del} = require('./api/stripeRequests');
const {retrieve, list, create, update, del} = require('./api/stripeRequests');
const api = require('./api');

const STRIPE_API_VERSION = '2019-09-09';
Expand Down Expand Up @@ -135,6 +135,15 @@ module.exports = class StripePaymentProcessor {
return true;
}

async updateSubscriptionFromClient(subscription) {
const updatedSubscription = await update(this._stripe, 'subscriptions', subscription.id, {
cancel_at_period_end: subscription.cancel_at_period_end
});
await this._updateSubscription(updatedSubscription);

return updatedSubscription;
}

async getSubscriptions(member) {
const metadata = await this.storage.get(member);

Expand Down Expand Up @@ -162,6 +171,7 @@ module.exports = class StripePaymentProcessor {
status: subscription.status,
start_date: subscription.start_date,
default_payment_card_last4: subscription.default_payment_card_last4,
cancel_at_period_end: subscription.cancel_at_period_end,
current_period_end: subscription.current_period_end
};
});
Expand Down Expand Up @@ -235,6 +245,7 @@ module.exports = class StripePaymentProcessor {

subscription_id: subscription.id,
status: subscription.status,
cancel_at_period_end: subscription.cancel_at_period_end,
current_period_end: new Date(subscription.current_period_end * 1000),
start_date: new Date(subscription.start_date * 1000),
default_payment_card_last4: payment && payment.card && payment.card.last4 || null,
Expand Down