infoalloggi-monorepo/apps/infoalloggi/src/pages/api/stripe-webhook.ts

140 lines
3.3 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import type { NextApiRequest, NextApiResponse } from "next";
import type Stripe from "stripe";
import { env } from "~/env";
import stripe from "~/lib/stripe";
2025-08-04 17:45:44 +02:00
import type { PaymentsId } from "~/schemas/public/Payments";
import { appRouter } from "~/server/api/root";
import { createTRPCContext, t } from "~/server/api/trpc";
const handler = async (
2025-08-28 18:27:07 +02:00
req: NextApiRequest,
res: NextApiResponse,
): Promise<void> => {
2025-08-28 18:27:07 +02:00
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: Stripe.Event;
2025-08-28 18:27:07 +02:00
if (!sig) {
res.status(400).send("Webhook Error: Missing Stripe signature");
return;
}
try {
const body = await buffer(req);
event = stripe.webhooks.constructEvent(
body,
sig,
env.STRIPE_WEBHOOK_SECRET,
);
2025-08-28 18:27:07 +02:00
} catch (err) {
console.error("Error verifying webhook signature:", err);
res.status(400).send(`Webhook Error: ${(err as Error).message}`);
return;
2025-08-28 18:27:07 +02:00
}
// Handle the event
switch (event.type) {
case "payment_intent.created": {
const paymentIntent = event.data.object;
const { id, metadata } = paymentIntent;
if (!metadata) {
res.status(400).send("No metadata in payment intent");
return;
2025-08-28 18:27:07 +02:00
}
if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) {
res.status(400).send("No orderId or userId in metadata");
return;
2025-08-28 18:27:07 +02:00
}
await caller.stripe.whIntentCreated({
paymentId: metadata.paymentId as PaymentsId,
2025-08-29 16:18:32 +02:00
pIntentId: id,
2025-08-28 18:27:07 +02:00
});
break;
}
case "payment_intent.succeeded": {
const paymentIntent = event.data.object;
const { id, metadata } = paymentIntent;
if (!metadata) {
res.status(400).send("No metadata in payment intent");
return;
2025-08-28 18:27:07 +02:00
}
if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) {
res.status(400).send("No orderId or userId in metadata");
return;
2025-08-28 18:27:07 +02:00
}
await caller.stripe.whIntentSucceeded({
paymentId: metadata.paymentId as PaymentsId,
2025-08-29 16:18:32 +02:00
pIntentId: id,
2025-08-28 18:27:07 +02:00
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");
}
};
2025-08-04 17:45:44 +02:00
export const config = {
2025-08-28 18:27:07 +02:00
api: {
bodyParser: false,
},
2025-08-04 17:45:44 +02:00
};
const buffer = (req: NextApiRequest) => {
2025-08-28 18:27:07 +02:00
return new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
req.on("end", () => {
resolve(Buffer.concat(chunks));
});
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
req.on("error", reject);
});
2025-08-04 17:45:44 +02:00
};
export default handler;