Stripe: upgrade module and API, switch to Checkout (#12785)

* upgrade stripe module

* switch stripe api to latest version

* fix api version in tests

* start upgrading client and server

* client: switch to redirect

* implement checkout session creation for gems, start implementing webhooks

* stripe: start refactoring one time payments

* working gems and gift payments

* start adding support for subscriptions

* stripe: migrate subscriptions and fix cancelling sub

* allow upgrading group plans

* remove console.log statements

* group plans: upgrade from static page / create new one

* fix #11885, correct group plan modal title

* silence more stripe webhooks

* fix group plans redirects

* implement editing payment method

* start cleaning up code

* fix(stripe): update in-code docs, fix eslint issues

* subscriptions tests

* remove and skip old tests

* skip integration tests

* fix client build

* stripe webhooks: throw error if request fails

* subscriptions: correctly pass groupId

* remove console.log

* stripe: add unit tests for one time payments

* wip: stripe checkout tests

* stripe createCheckoutSession unit tests

* stripe createCheckoutSession unit tests

* stripe createCheckoutSession unit tests (editing card)

* fix existing webhooks tests

* add new webhooks tests

* add more webhooks tests

* fix lint

* stripe integration tests

* better error handling when retrieving customer from stripe

* client: remove unused strings and improve error handling

* payments: limit gift message length (server)

* payments: limit gift message length (client)

* fix redirects when payment is cancelled

* add back "subUpdateCard" string

* fix redirects when editing a sub card, use proper names for products, check subs when gifting
This commit is contained in:
Matteo Pagliazzi
2020-12-14 15:59:17 +01:00
parent 5daf96bbf5
commit 2d091fc667
53 changed files with 2457 additions and 1661 deletions
+24 -31
View File
@@ -198,8 +198,7 @@ api.createGroupPlan = {
const results = await Promise.all([user.save(), group.save()]);
const savedGroup = results[1];
// Analytics
const analyticsObject = {
res.analytics.track('join group', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
@@ -207,27 +206,31 @@ api.createGroupPlan = {
groupType: savedGroup.type,
privacy: savedGroup.privacy,
headers: req.headers,
});
// do not remove chat flags data as we've just created the group
const groupResponse = savedGroup.toJSON();
// the leader is the authenticated user
groupResponse.leader = {
_id: user._id,
profile: { name: user.profile.name },
};
res.analytics.track('join group', analyticsObject);
if (req.body.paymentType === 'Stripe') {
const token = req.body.id;
const gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
const sub = req.query.sub ? common.content.subscriptionBlocks[req.query.sub] : false;
const groupId = savedGroup._id;
const { email } = req.body;
const { headers } = req;
const { coupon } = req.query;
const {
gift, sub: subKey, gemsBlock, coupon,
} = req.body;
await stripePayments.checkout({
token,
user,
gift,
sub,
groupId,
email,
headers,
coupon,
const sub = subKey ? common.content.subscriptionBlocks[subKey] : false;
const groupId = savedGroup._id;
const session = await stripePayments.createCheckoutSession({
user, gemsBlock, gift, sub, groupId, coupon, headers: req.headers,
});
res.respond(200, {
sessionId: session.id,
group: groupResponse,
});
} else if (req.body.paymentType === 'Amazon') {
const { billingAgreementId } = req.body;
@@ -246,19 +249,9 @@ api.createGroupPlan = {
groupId,
headers,
});
res.respond(201, groupResponse);
}
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]);
// doc.populate doesn't return a promise
const response = savedGroup.toJSON();
// the leader is the authenticated user
response.leader = {
_id: user._id,
profile: { name: user.profile.name },
};
res.respond(201, response); // do not remove chat flags data as we've just created the group
},
};
@@ -8,37 +8,37 @@ const api = {};
/**
* @apiIgnore Payments are considered part of the private API
* @api {post} /stripe/checkout Stripe checkout
* @api {post} /stripe/checkout-session Create a Stripe Checkout Session
* @apiName StripeCheckout
* @apiGroup Payments
*
* @apiParam {String} id Body parameter - The token
* @apiParam {String} email Body parameter - the customer email
* @apiParam {String} gift Query parameter - stringified json object, gift
* @apiParam {String} sub Query parameter - subscription, possible values are:
* basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo
* @apiParam {String} coupon Query parameter - coupon for the matching subscription,
* required only for certain subscriptions
* @apiParam (Body) {String} [gemsBlock] If purchasing a gem block, its key
* @apiParam (Body) {Object} [gift] The gift object
* @apiParam (Body) {String} [sub] If purchasing a subscription, its key
* @apiParam (Body) {UUID} [groupId] If purchasing a group plan, the group id
* @apiParam (Body) {String} [coupon] Subscription Coupon
*
* @apiSuccess {Object} data Empty object
* @apiSuccess {String} data.sessionId The created checkout session id
* */
api.checkout = {
api.createCheckoutSession = {
method: 'POST',
url: '/stripe/checkout',
url: '/stripe/checkout-session',
middlewares: [authWithHeaders()],
async handler (req, res) {
// @TODO: These quer params need to be changed to body
const token = req.body.id;
const { user } = res.locals;
const gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
const sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false;
const { groupId, coupon, gemsBlock } = req.query;
const {
gift, sub: subKey, gemsBlock, coupon, groupId,
} = req.body;
await stripePayments.checkout({
token, user, gemsBlock, gift, sub, groupId, coupon, headers: req.headers,
const sub = subKey ? shared.content.subscriptionBlocks[subKey] : false;
const session = await stripePayments.createCheckoutSession({
user, gemsBlock, gift, sub, groupId, coupon,
});
res.respond(200, {});
res.respond(200, {
sessionId: session.id,
});
},
};
@@ -48,22 +48,23 @@ api.checkout = {
* @apiName StripeSubscribeEdit
* @apiGroup Payments
*
* @apiParam {String} id Body parameter - The token
* @apiParam (Body) {UUID} [groupId] If editing a group plan, the group id
*
* @apiSuccess {Object} data Empty object
* @apiSuccess {String} data.sessionId The created checkout session id
* */
api.subscribeEdit = {
method: 'POST',
url: '/stripe/subscribe/edit',
middlewares: [authWithHeaders()],
async handler (req, res) {
const token = req.body.id;
const { groupId } = req.body;
const { user } = res.locals;
await stripePayments.editSubscription({ token, groupId, user });
const session = await stripePayments.createEditCardCheckoutSession({ groupId, user });
res.respond(200, {});
res.respond(200, {
sessionId: session.id,
});
},
};
@@ -72,6 +73,9 @@ api.subscribeEdit = {
* @api {get} /stripe/subscribe/cancel Cancel Stripe subscription
* @apiName StripeSubscribeCancel
* @apiGroup Payments
*
* @apiParam (Body) {UUID} [groupId] If editing a group plan, the group id
*
* */
api.subscribeCancel = {
method: 'GET',
@@ -91,11 +95,20 @@ api.subscribeCancel = {
},
};
// NOTE: due to Stripe requirements on validating webhooks, the body is not json parsed
// for this route, see website/server/middlewares/index.js
/**
* @apiIgnore Payments are considered part of the private API
* @api {post} /stripe/webhooks Stripe Webhooks handler
* @apiName StripeHandleWebhooks
* @apiGroup Payments
* */
api.handleWebhooks = {
method: 'POST',
url: '/stripe/webhooks',
async handler (req, res) {
await stripePayments.handleWebhooks({ requestBody: req.body });
await stripePayments.handleWebhooks({ body: req.body, headers: req.headers });
return res.respond(200, {});
},