import type { NextApiRequest, NextApiResponse } from "next"; import Stripe from "stripe"; import { env } from "~/env.mjs"; import type { PaymentsId } from "~/schemas/public/Payments"; import { appRouter } from "~/server/api/root"; import { createTRPCContext, t } from "~/server/api/trpc"; const stripe = new Stripe(env.STRIPE_SECRET_KEY); const endpointSecret = env.STRIPE_WEBHOOK_SECRET; export default async function handler( req: NextApiRequest, res: NextApiResponse, ) { if (req.method === "POST") { const ctx = await createTRPCContext({ req: req, res: res }); const createCaller = t.createCallerFactory(appRouter); const caller = createCaller(ctx); const sig = req.headers["stripe-signature"]; let event; if (!sig) { res.status(400).send("Webhook Error: Missing Stripe signature"); return; } try { const body = await buffer(req); event = stripe.webhooks.constructEvent(body, sig, endpointSecret); } catch (err) { console.error("Error verifying webhook signature:", err); return res.status(400).send(`Webhook Error: ${(err as Error).message}`); } // Handle the event // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (event.type) { case "payment_intent.created": { const paymentIntent = event.data.object; const { id, metadata } = paymentIntent; if (!metadata) { return res.status(400).send("No metadata in payment intent"); } if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) { return res.status(400).send("No orderId or userId in metadata"); } await caller.stripe.whIntentCreated({ pIntentId: id, paymentId: metadata.paymentId as PaymentsId, }); break; } case "payment_intent.succeeded": { const paymentIntent = event.data.object; const { id, metadata } = paymentIntent; if (!metadata) { return res.status(400).send("No metadata in payment intent"); } if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) { return res.status(400).send("No orderId or userId in metadata"); } await caller.stripe.whIntentSucceeded({ pIntentId: id, paymentId: metadata.paymentId as PaymentsId, pm_id: typeof paymentIntent.payment_method == "string" ? paymentIntent.payment_method : "", }); break; } case "payment_intent.canceled": { const paymentIntent = event.data.object; await caller.stripe.whIntentFailed({ pIntentId: paymentIntent.id, }); break; } case "payment_intent.payment_failed": { const paymentIntent = event.data.object; await caller.stripe.whIntentFailed({ pIntentId: paymentIntent.id, }); break; } case "payment_intent.processing": { const paymentIntent = event.data.object; console.log("PaymentIntent processing", paymentIntent); break; } default: console.log(`Unhandled event type: ${event.type}`); } res.status(200).json({ received: true }); } else { res.setHeader("Allow", "POST"); res.status(405).end("Method Not Allowed"); } } export const config = { api: { bodyParser: false, }, }; const buffer = (req: NextApiRequest) => { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; req.on("data", (chunk: Buffer) => { chunks.push(chunk); }); req.on("end", () => { resolve(Buffer.concat(chunks)); }); req.on("error", reject); }); };