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;
|
||||
isCloud: boolean;
|
||||
stripeSecretKey: string;
|
||||
stripeSigningSecret: string;
|
||||
stripeStarterPriceKey: string;
|
||||
stripeGrowthPriceKey: string;
|
||||
licenseKey: string;
|
||||
@@ -110,6 +111,7 @@ const appConfig: AppConfig = {
|
||||
fromEmail: process.env.FROM_EMAIL,
|
||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
stripeSigningSecret: process.env.STRIPE_SIGNING_SECRET,
|
||||
stripeStarterPriceKey: process.env.STRIPE_STARTER_PRICE_KEY,
|
||||
stripeGrowthPriceKey: process.env.STRIPE_GROWTH_PRICE_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 UsageData from '../../models/usage-data.ee';
|
||||
import appConfig from '../../config/app';
|
||||
import handleWebhooks from './webhooks.ee';
|
||||
|
||||
const plans = [
|
||||
{
|
||||
@@ -91,6 +92,9 @@ const createPaymentPortalUrl = async (user: User) => {
|
||||
const billing = {
|
||||
createSubscription,
|
||||
createPaymentPortalUrl,
|
||||
handleWebhooks,
|
||||
stripe,
|
||||
plans,
|
||||
};
|
||||
|
||||
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;
|
||||
currentPeriodStartedAt!: string;
|
||||
currentPeriodEndsAt!: string;
|
||||
user?: User;
|
||||
|
||||
static tableName = 'payment_plans';
|
||||
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import { Router } from 'express';
|
||||
import graphQLInstance from '../helpers/graphql-instance';
|
||||
import webhooksRouter from './webhooks';
|
||||
import stripeRouter from './stripe.ee';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/graphql', graphQLInstance);
|
||||
router.use('/webhooks', webhooksRouter);
|
||||
router.use('/stripe', stripeRouter);
|
||||
|
||||
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