import { headers } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; import { stripe } from '@/lib/stripe'; import { syncSubscriptionFromStripe, handleSubscriptionDeleted, resolveUserIdFromStripeEvent, } from '@/lib/billing/sync-subscription-from-stripe'; import { prisma } from '@/lib/prisma'; import type Stripe from 'stripe'; export const runtime = 'nodejs'; export async function POST(req: NextRequest) { const body = await req.text(); const headersList = await headers(); const sig = headersList.get('stripe-signature'); if (!sig) { return NextResponse.json({ error: 'Missing stripe-signature header' }, { status: 400 }); } if (!process.env.STRIPE_WEBHOOK_SECRET) { console.error('[billing/webhook] STRIPE_WEBHOOK_SECRET not configured'); return NextResponse.json({ error: 'Webhook not configured' }, { status: 500 }); } let event: Stripe.Event; try { event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { console.error('[billing/webhook] Signature verification failed:', err); return NextResponse.json({ error: 'Invalid signature' }, { status: 400 }); } try { switch (event.type) { case 'checkout.session.completed': { const session = event.data.object as Stripe.Checkout.Session; if (session.mode === 'subscription' && session.subscription) { const subscriptionId = typeof session.subscription === 'string' ? session.subscription : session.subscription.id; const subscription = await stripe.subscriptions.retrieve(subscriptionId); const userId = (session.metadata?.userId as string | undefined) ?? (subscription.metadata?.userId as string | undefined); if (userId) { await syncSubscriptionFromStripe(subscription, userId); } else { console.warn('[billing/webhook] checkout.session.completed: no userId in metadata', session.id); } } break; } case 'customer.subscription.created': case 'customer.subscription.updated': { const subscription = event.data.object as Stripe.Subscription; const userId = await resolveUserIdFromStripeEvent(subscription); if (userId) { await syncSubscriptionFromStripe(subscription, userId); } else { console.warn('[billing/webhook] subscription event: no userId found', subscription.id); } break; } case 'customer.subscription.deleted': { const subscription = event.data.object as Stripe.Subscription; await handleSubscriptionDeleted(subscription); break; } case 'invoice.payment_failed': { const invoice = event.data.object as Stripe.Invoice & { subscription?: string | { id: string } }; if (invoice.subscription) { const subscriptionId = typeof invoice.subscription === 'string' ? invoice.subscription : invoice.subscription.id; const subscription = await stripe.subscriptions.retrieve(subscriptionId); const userId = await resolveUserIdFromStripeEvent(subscription); if (userId) { await syncSubscriptionFromStripe(subscription, userId); } } break; } default: break; } return NextResponse.json({ received: true }); } catch (error) { console.error('[billing/webhook] Handler error:', error); return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 }); } }