Files
calendar/apps/web/pages/api/integrations/subscriptions/webhook.ts
T
a804a29516 feat: Stripe paid apps flow (#12103)
* chore: Stripe paid apps flow

* chore: Subscription

* chore: Webhooks

* chore: Abstract functions

* chore: Lockfile

* chore: Webhook handler

* chore: Use catch-all

* chore: Webhook changes, etc

* chore: Cleanup

* chore: Use actual price id

* chore: Updates

* chore: Install normally until expiry date

* Disable team install for paid apps and cal.ai\

* Fix the same at another place

* Fix Typescript error

* redactedCause doesnt have message has enumerable prop

* Fix reinstallation of an already installed app

* chore: Remove unused deps

* chore: Ensure index

* chore: Price in usd

* chore: PR suggestion

* Fix missing packages in yarn.lock

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-15 09:29:41 -03:00

117 lines
3.4 KiB
TypeScript

import { buffer } from "micro";
import type { NextApiRequest, NextApiResponse } from "next";
import type Stripe from "stripe";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
export const config = {
api: {
bodyParser: false,
},
};
// This file is a catch-all for any integration related subscription/paid app.
const handleSubscriptionUpdate = async (event: Stripe.Event) => {
const subscription = event.data.object as Stripe.Subscription;
if (!subscription.id) throw new HttpCode({ statusCode: 400, message: "Subscription ID not found" });
const app = await prisma.credential.findFirst({
where: {
subscriptionId: subscription.id,
},
});
if (!app) {
throw new HttpCode({ statusCode: 202, message: "Received and discarded" });
}
await prisma.credential.update({
where: {
id: app.id,
},
data: {
paymentStatus: subscription.status,
},
});
};
const handleSubscriptionDeleted = async (event: Stripe.Event) => {
const subscription = event.data.object as Stripe.Subscription;
if (!subscription.id) throw new HttpCode({ statusCode: 400, message: "Subscription ID not found" });
const app = await prisma.credential.findFirst({
where: {
subscriptionId: subscription.id,
},
});
if (!app) {
throw new HttpCode({ statusCode: 202, message: "Received and discarded" });
}
// should we delete the credential here rather than marking as inactive?
await prisma.credential.update({
where: {
id: app.id,
},
data: {
paymentStatus: "inactive",
billingCycleStart: null,
},
});
};
type WebhookHandler = (event: Stripe.Event) => Promise<void>;
const webhookHandlers: Record<string, WebhookHandler | undefined> = {
"customer.subscription.updated": handleSubscriptionUpdate,
"customer.subscription.deleted": handleSubscriptionDeleted,
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
}
const sig = req.headers["stripe-signature"];
if (!sig) {
throw new HttpCode({ statusCode: 400, message: "Missing stripe-signature" });
}
if (!process.env.STRIPE_WEBHOOK_SECRET) {
throw new HttpCode({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET" });
}
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
const handler = webhookHandlers[event.type];
if (handler) {
await handler(event);
} else {
/** Not really an error, just letting Stripe know that the webhook was received but unhandled */
throw new HttpCode({
statusCode: 202,
message: `Unhandled Stripe Webhook event type ${event.type}`,
});
}
} catch (_err) {
const err = getErrorFromUnknown(_err);
console.error(`Webhook Error: ${err.message}`);
res.status(err.statusCode ?? 500).send({
message: err.message,
stack: IS_PRODUCTION ? undefined : err.stack,
});
return;
}
// Return a response to acknowledge receipt of the event
res.json({ received: true });
}