Merge pull request #983 from automatisch/payment-webhooks
feat: Implement stripe webhooks
This commit is contained in:
@@ -40,6 +40,7 @@ type AppConfig = {
|
|||||||
fromEmail: string;
|
fromEmail: string;
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
stripeSecretKey: string;
|
stripeSecretKey: string;
|
||||||
|
stripeSigningSecret: string;
|
||||||
stripeStarterPriceKey: string;
|
stripeStarterPriceKey: string;
|
||||||
stripeGrowthPriceKey: string;
|
stripeGrowthPriceKey: string;
|
||||||
licenseKey: string;
|
licenseKey: string;
|
||||||
@@ -110,6 +111,7 @@ const appConfig: AppConfig = {
|
|||||||
fromEmail: process.env.FROM_EMAIL,
|
fromEmail: process.env.FROM_EMAIL,
|
||||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||||
|
stripeSigningSecret: process.env.STRIPE_SIGNING_SECRET,
|
||||||
stripeStarterPriceKey: process.env.STRIPE_STARTER_PRICE_KEY,
|
stripeStarterPriceKey: process.env.STRIPE_STARTER_PRICE_KEY,
|
||||||
stripeGrowthPriceKey: process.env.STRIPE_GROWTH_PRICE_KEY,
|
stripeGrowthPriceKey: process.env.STRIPE_GROWTH_PRICE_KEY,
|
||||||
licenseKey: process.env.LICENSE_KEY,
|
licenseKey: process.env.LICENSE_KEY,
|
||||||
|
23
packages/backend/src/controllers/stripe/webhooks.ee.ts
Normal file
23
packages/backend/src/controllers/stripe/webhooks.ee.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Response } from 'express';
|
||||||
|
import { IRequest } from '@automatisch/types';
|
||||||
|
import Billing from '../../helpers/billing/index.ee';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
import logger from '../../helpers/logger';
|
||||||
|
|
||||||
|
export default async (request: IRequest, response: Response) => {
|
||||||
|
const signature = request.headers['stripe-signature'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const event = Billing.stripe.webhooks.constructEvent(
|
||||||
|
request.rawBody,
|
||||||
|
signature,
|
||||||
|
appConfig.stripeSigningSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
await Billing.handleWebhooks(event);
|
||||||
|
return response.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Webhook Error: ${error.message}`);
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
};
|
@@ -3,6 +3,7 @@ import User from '../../models/user';
|
|||||||
import PaymentPlan from '../../models/payment-plan.ee';
|
import PaymentPlan from '../../models/payment-plan.ee';
|
||||||
import UsageData from '../../models/usage-data.ee';
|
import UsageData from '../../models/usage-data.ee';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
|
import handleWebhooks from './webhooks.ee';
|
||||||
|
|
||||||
const plans = [
|
const plans = [
|
||||||
{
|
{
|
||||||
@@ -91,6 +92,9 @@ const createPaymentPortalUrl = async (user: User) => {
|
|||||||
const billing = {
|
const billing = {
|
||||||
createSubscription,
|
createSubscription,
|
||||||
createPaymentPortalUrl,
|
createPaymentPortalUrl,
|
||||||
|
handleWebhooks,
|
||||||
|
stripe,
|
||||||
|
plans,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default billing;
|
export default billing;
|
||||||
|
42
packages/backend/src/helpers/billing/webhooks.ee.ts
Normal file
42
packages/backend/src/helpers/billing/webhooks.ee.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import Stripe from 'stripe';
|
||||||
|
import PaymentPlan from '../../models/payment-plan.ee';
|
||||||
|
import Billing from './index.ee';
|
||||||
|
|
||||||
|
const handleWebhooks = async (event: Stripe.Event) => {
|
||||||
|
const trackedWebhookTypes = [
|
||||||
|
'customer.subscription.created',
|
||||||
|
'customer.subscription.updated',
|
||||||
|
'customer.subscription.deleted',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!trackedWebhookTypes.includes(event.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updatePaymentPlan(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePaymentPlan = async (event: Stripe.Event) => {
|
||||||
|
const subscription = event.data.object as Stripe.Subscription;
|
||||||
|
const priceKey = subscription.items.data[0].plan.id;
|
||||||
|
const plan = Billing.plans.find((plan) => plan.price === priceKey);
|
||||||
|
|
||||||
|
const paymentPlan = await PaymentPlan.query().findOne({
|
||||||
|
stripe_customer_id: subscription.customer,
|
||||||
|
});
|
||||||
|
|
||||||
|
await paymentPlan.$query().patchAndFetch({
|
||||||
|
name: plan.name,
|
||||||
|
taskCount: plan.taskCount,
|
||||||
|
stripeSubscriptionId: subscription.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await paymentPlan.$relatedQuery('user');
|
||||||
|
const usageData = await user.$relatedQuery('usageData');
|
||||||
|
|
||||||
|
await usageData.$query().patchAndFetch({
|
||||||
|
nextResetAt: new Date(subscription.current_period_end * 1000).toISOString(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleWebhooks;
|
@@ -10,6 +10,7 @@ class PaymentPlan extends Base {
|
|||||||
stripeSubscriptionId!: string;
|
stripeSubscriptionId!: string;
|
||||||
currentPeriodStartedAt!: string;
|
currentPeriodStartedAt!: string;
|
||||||
currentPeriodEndsAt!: string;
|
currentPeriodEndsAt!: string;
|
||||||
|
user?: User;
|
||||||
|
|
||||||
static tableName = 'payment_plans';
|
static tableName = 'payment_plans';
|
||||||
|
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import graphQLInstance from '../helpers/graphql-instance';
|
import graphQLInstance from '../helpers/graphql-instance';
|
||||||
import webhooksRouter from './webhooks';
|
import webhooksRouter from './webhooks';
|
||||||
|
import stripeRouter from './stripe.ee';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/graphql', graphQLInstance);
|
router.use('/graphql', graphQLInstance);
|
||||||
router.use('/webhooks', webhooksRouter);
|
router.use('/webhooks', webhooksRouter);
|
||||||
|
router.use('/stripe', stripeRouter);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
23
packages/backend/src/routes/stripe.ee.ts
Normal file
23
packages/backend/src/routes/stripe.ee.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import express, { Router } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
import { IRequest } from '@automatisch/types';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import stripeWebhooksAction from '../controllers/stripe/webhooks.ee';
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
const upload = multer();
|
||||||
|
|
||||||
|
router.use(upload.none());
|
||||||
|
|
||||||
|
router.use(
|
||||||
|
express.text({
|
||||||
|
limit: appConfig.requestBodySizeLimit,
|
||||||
|
verify(req, res, buf) {
|
||||||
|
(req as IRequest).rawBody = buf;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post('/webhooks', stripeWebhooksAction);
|
||||||
|
|
||||||
|
export default router;
|
Reference in New Issue
Block a user