habitica/website/server/libs/payments/stripe/webhooks.js

135 lines
4.7 KiB
JavaScript
Raw Normal View History

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
2020-12-14 14:59:17 +00:00
import nconf from 'nconf';
import moment from 'moment';
import logger from '../../logger';
import { model as User } from '../../../models/user'; // eslint-disable-line import/no-cycle
import { getStripeApi } from './api';
import {
BadRequest,
NotFound,
} from '../../errors';
import payments from '../payments'; // eslint-disable-line import/no-cycle
import { // eslint-disable-line import/no-cycle
model as Group,
basicFields as basicGroupFields,
} from '../../../models/group';
import shared from '../../../../common';
import { applyGemPayment, applySku } from './oneTimePayments'; // eslint-disable-line import/no-cycle
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
2020-12-14 14:59:17 +00:00
import { applySubscription, handlePaymentMethodChange } from './subscriptions'; // eslint-disable-line import/no-cycle
const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET');
export async function handleWebhooks (options, stripeInc) {
const { body, headers } = options;
// @TODO: We need to mock this, but curently we don't have correct
// Dependency Injection. And the Stripe Api doesn't seem to be a singleton?
let stripeApi = getStripeApi();
if (stripeInc) stripeApi = stripeInc;
let event;
try {
// Verify the event by fetching it from Stripe
event = stripeApi.webhooks.constructEvent(body, headers['stripe-signature'], endpointSecret);
} catch (err) {
logger.error(new Error('Error verifying Stripe webhook'), { err });
throw new BadRequest(`Webhook Error: ${err.message}`);
}
try {
switch (event.type) {
case 'payment_intent.created':
case 'payment_intent.succeeded':
case 'payment_intent.payment_failed':
case 'setup_intent.created':
case 'setup_intent.succeeded':
case 'charge.succeeded':
case 'charge.failed':
case 'payment_method.attached':
case 'customer.created':
case 'customer.updated':
case 'customer.deleted':
case 'customer.subscription.created':
case 'customer.subscription.updated':
case 'invoiceitem.created':
case 'invoice.created':
case 'invoice.updated':
case 'invoice.finalized':
case 'invoice.paid':
case 'invoice.upcoming':
case 'invoice.payment_succeeded': {
// Events sent even if not active in the Stripe dashboard when a payment is being made
// This is to avoid error logs from the webhook handler not being implemented
break;
}
case 'checkout.session.completed': {
const session = event.data.object;
const { metadata } = session;
if (metadata.type === 'edit-card-group' || metadata.type === 'edit-card-user') {
await handlePaymentMethodChange(session);
} else if (metadata.type === 'subscription') {
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
2020-12-14 14:59:17 +00:00
await applySubscription(session);
} else if (metadata.type === 'sku') {
await applySku(session);
} else {
await applyGemPayment(session);
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
2020-12-14 14:59:17 +00:00
}
break;
}
case 'customer.subscription.deleted': {
// event.request !== null means that the user itself cancelled the subscrioption,
// the cancellation on our side has been already handled
if (event.request && event.request.id !== null) break;
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
2020-12-14 14:59:17 +00:00
const subscription = event.data.object;
const customerId = subscription.customer;
const isGroupSub = shared.content.subscriptionBlocks[subscription.plan.id].target === 'group';
let user;
let groupId;
if (isGroupSub) {
const groupFields = basicGroupFields.concat(' purchased');
const group = await Group.findOne({
'purchased.plan.customerId': customerId,
'purchased.plan.paymentMethod': this.constants.PAYMENT_METHOD,
}).select(groupFields).exec();
if (!group) throw new NotFound(shared.i18n.t('groupNotFound'));
groupId = group._id;
user = await User.findById(group.leader).exec();
} else {
user = await User.findOne({
'purchased.plan.customerId': customerId,
'purchased.plan.paymentMethod': this.constants.PAYMENT_METHOD,
}).exec();
}
if (!user) throw new NotFound(shared.i18n.t('userNotFound'));
await stripeApi.customers.del(customerId);
await payments.cancelSubscription({
user,
groupId,
paymentMethod: this.constants.PAYMENT_METHOD,
// Give three extra days to allow the user to resubscribe without losing benefits
nextBill: moment().add({ days: 3 }).toDate(),
});
break;
}
default: {
throw new BadRequest(`Missing handler for Stripe webhook ${event.type}`);
}
}
} catch (err) {
logger.error(new Error('Error handling Stripe webhook'), { event, err });
throw err;
}
}