infoalloggi-monorepo/apps/infoalloggi/src/pages/api/stripe-webhook.ts
Marco Pedone 699579b432 feat: update environment variable handling and dependencies
- Added @t3-oss/env-nextjs for improved environment variable management.
- Updated zod to version 4.1.12 for enhanced validation capabilities.
- Upgraded jiti to version 2.6.1 for better module loading.
- Refactored environment variable imports from env.mjs to env.ts.
- Removed deprecated env.mjs file and replaced it with a new env.ts file using @t3-oss/env-nextjs.
- Adjusted various components and API routes to utilize the new environment variable structure.
- Updated next.config.js to support the new environment variable management.
- Modified Docker configuration to align with new BASE_URL handling.
2025-10-20 16:22:20 +02:00

140 lines
3.3 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
import { env } from "~/env";
import type { PaymentsId } from "~/schemas/public/Payments";
import { appRouter } from "~/server/api/root";
import { createTRPCContext, t } from "~/server/api/trpc";
const handler = async (
req: NextApiRequest,
res: NextApiResponse,
): Promise<void> => {
const stripe = new Stripe(env.STRIPE_SECRET_KEY);
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;
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,
);
} catch (err) {
console.error("Error verifying webhook signature:", err);
res.status(400).send(`Webhook Error: ${(err as Error).message}`);
return;
}
// 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;
}
if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) {
res.status(400).send("No orderId or userId in metadata");
return;
}
await caller.stripe.whIntentCreated({
paymentId: metadata.paymentId as PaymentsId,
pIntentId: id,
});
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;
}
if (!metadata.paymentId || !(typeof metadata.paymentId === "string")) {
res.status(400).send("No orderId or userId in metadata");
return;
}
await caller.stripe.whIntentSucceeded({
paymentId: metadata.paymentId as PaymentsId,
pIntentId: id,
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<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
req.on("end", () => {
resolve(Buffer.concat(chunks));
});
req.on("error", reject);
});
};
export default handler;