import { NextResponse } from "next/server"; import type Stripe from "stripe"; import { isPaidPlanId, type PlanId } from "@/lib/plans"; import { getStripe } from "@/lib/stripe"; import { createAdminClient } from "@/lib/supabase/admin"; export const runtime = "nodejs"; function resolvePlanId(metadata: Stripe.Metadata | null): PlanId | null { const planId = metadata?.planId; if (planId && isPaidPlanId(planId)) { return planId; } return null; } async function upsertProfileFromSession(session: Stripe.Checkout.Session) { const userId = session.client_reference_id ?? session.metadata?.userId; if (!userId) { return; } const plan = resolvePlanId(session.metadata); if (!plan) { return; } const admin = createAdminClient(); const { error } = await admin.from("profiles").upsert( { id: userId, email: session.customer_email ?? session.customer_details?.email ?? null, plan, billing_period: session.metadata?.billingPeriod ?? null, stripe_customer_id: typeof session.customer === "string" ? session.customer : null, stripe_subscription_id: typeof session.subscription === "string" ? session.subscription : null, updated_at: new Date().toISOString(), }, { onConflict: "id" } ); if (error) { throw new Error(`Failed to update profile: ${error.message}`); } } export async function POST(request: Request) { const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!webhookSecret) { return NextResponse.json( { error: "Webhook secret not configured." }, { status: 500 } ); } const signature = request.headers.get("stripe-signature"); if (!signature) { return NextResponse.json( { error: "Missing stripe-signature header." }, { status: 400 } ); } const body = await request.text(); const stripe = getStripe(); let event: Stripe.Event; try { event = stripe.webhooks.constructEvent(body, signature, webhookSecret); } catch (error) { const message = error instanceof Error ? error.message : "Webhook signature verification failed."; return NextResponse.json({ error: message }, { status: 400 }); } try { switch (event.type) { case "checkout.session.completed": { const session = event.data.object as Stripe.Checkout.Session; if (session.mode === "subscription") { await upsertProfileFromSession(session); } break; } case "customer.subscription.deleted": { const subscription = event.data.object as Stripe.Subscription; const userId = subscription.metadata?.userId; if (userId) { const admin = createAdminClient(); await admin .from("profiles") .update({ plan: "free", billing_period: null, stripe_subscription_id: null, updated_at: new Date().toISOString(), }) .eq("id", userId); } break; } default: break; } } catch (error) { const message = error instanceof Error ? error.message : "Webhook handler failed."; return NextResponse.json({ error: message }, { status: 500 }); } return NextResponse.json({ received: true }); }