2026-06-07 03:58:32 -04:00
|
|
|
import { NextRequest } from "next/server";
|
|
|
|
|
import type Stripe from "stripe";
|
|
|
|
|
import { stripe } from "@/lib/billing/stripe";
|
|
|
|
|
import { handleStripeEvent } from "@/lib/billing/webhooks/stripe";
|
2026-06-07 17:54:30 -04:00
|
|
|
import { alreadyProcessed, logWebhook } from "@/lib/billing/webhook-log";
|
2026-06-07 03:58:32 -04:00
|
|
|
|
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
|
|
|
|
|
|
export async function POST(req: NextRequest) {
|
|
|
|
|
const secret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
|
|
|
const signature = req.headers.get("stripe-signature");
|
|
|
|
|
if (!secret || !signature) return new Response("Webhook not configured", { status: 400 });
|
|
|
|
|
|
|
|
|
|
const body = await req.text();
|
|
|
|
|
let event: Stripe.Event;
|
|
|
|
|
try {
|
|
|
|
|
event = stripe().webhooks.constructEvent(body, signature, secret);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("[stripe webhook] signature verification failed", err);
|
|
|
|
|
return new Response("Invalid signature", { status: 400 });
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 17:54:30 -04:00
|
|
|
// Idempotency: skip events we've already processed (Stripe retries deliveries).
|
|
|
|
|
if (await alreadyProcessed(event.id)) return new Response("ok (duplicate)");
|
|
|
|
|
|
2026-06-07 03:58:32 -04:00
|
|
|
try {
|
|
|
|
|
await handleStripeEvent(event);
|
2026-06-07 17:54:30 -04:00
|
|
|
await logWebhook("stripe", event.id, event.type, "processed");
|
2026-06-07 03:58:32 -04:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error("[stripe webhook] handler error", err);
|
2026-06-07 17:54:30 -04:00
|
|
|
await logWebhook("stripe", event.id, event.type, "failed", err instanceof Error ? err.message : String(err));
|
2026-06-07 03:58:32 -04:00
|
|
|
return new Response("Handler error", { status: 500 });
|
|
|
|
|
}
|
|
|
|
|
return new Response("ok");
|
|
|
|
|
}
|