import { NextResponse } from "next/server"; import { and, eq } from "drizzle-orm"; import { getMonthlyImageLimitByPlanId, isBillingPlanId, type BillingPlanId, } from "@/lib/billing/plans"; import { verifyStripeSignature } from "@/lib/billing/stripe"; import { db } from "@/lib/db"; import { user } from "@/lib/db/schema"; type StripeEvent = { id: string; type: string; data: { object: { metadata?: Record; customer_details?: { email?: string; }; mode?: string; status?: string; cancel_at_period_end?: boolean; }; }; }; async function applyPlanToUser({ userId, email, planId, }: { userId?: string; email?: string; planId: BillingPlanId; }) { const monthlyImageLimit = getMonthlyImageLimitByPlanId(planId); const updateData = { monthlyImageLimit, updatedAt: new Date(), }; if (userId) { await db.update(user).set(updateData).where(eq(user.id, userId)); return; } if (email) { await db .update(user) .set(updateData) .where(and(eq(user.email, email), eq(user.isActive, true))); } } function extractUserRef(object: StripeEvent["data"]["object"]) { return { userId: object.metadata?.userId, email: object.customer_details?.email, }; } export async function POST(request: Request) { const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!webhookSecret) { return NextResponse.json({ error: "Webhook secret is not configured" }, { status: 503 }); } const rawPayload = await request.text(); const signature = request.headers.get("stripe-signature"); if (!signature) { return NextResponse.json({ error: "Missing stripe-signature header" }, { status: 400 }); } const verified = verifyStripeSignature({ payload: rawPayload, signatureHeader: signature, webhookSecret, }); if (!verified) { return NextResponse.json({ error: "Invalid webhook signature" }, { status: 400 }); } try { const event = JSON.parse(rawPayload) as StripeEvent; const object = event.data.object; const userRef = extractUserRef(object); if (event.type === "checkout.session.completed") { if (object.mode !== "subscription") { return NextResponse.json({ received: true }); } const planId = object.metadata?.planId; if (planId && isBillingPlanId(planId)) { await applyPlanToUser({ ...userRef, planId }); } return NextResponse.json({ received: true }); } if (event.type === "customer.subscription.updated") { const planId = object.metadata?.planId; if (planId && isBillingPlanId(planId) && object.status === "active") { await applyPlanToUser({ ...userRef, planId }); } return NextResponse.json({ received: true }); } if (event.type === "customer.subscription.deleted") { await applyPlanToUser({ ...userRef, planId: "starter" }); return NextResponse.json({ received: true }); } return NextResponse.json({ received: true }); } catch (error) { console.error("[Billing Stripe Webhook] Error:", error); return NextResponse.json({ error: "Webhook processing failed" }, { status: 500 }); } }