import { NextResponse } from "next/server" import { eq } from "drizzle-orm" import { stripe } from "@/lib/stripe/client" import { db } from "@/lib/db" import { profiles, rent_payments } from "@/lib/db/schema" import type Stripe from "stripe" export async function POST(request: Request) { const body = await request.text() const sig = request.headers.get("stripe-signature")! let event: Stripe.Event try { event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!) } catch (err: unknown) { const message = err instanceof Error ? err.message : "Unknown error" return NextResponse.json({ error: `Webhook error: ${message}` }, { status: 400 }) } // Webhooks are not user-scoped: they identify the target row by the id / // customer id stored in Stripe metadata. There is no RLS to bypass anymore. switch (event.type) { case "checkout.session.completed": { const session = event.data.object as Stripe.Checkout.Session const userId = session.metadata?.supabase_user_id const plan = session.metadata?.plan if (!userId || !plan) break if (session.mode === "payment") { // Lifetime plan await db .update(profiles) .set({ plan: "lifetime", subscription_status: "active" }) .where(eq(profiles.id, userId)) } break } case "customer.subscription.created": case "customer.subscription.updated": { const subscription = event.data.object as Stripe.Subscription const userId = subscription.metadata?.supabase_user_id const plan = subscription.metadata?.plan if (!userId) break await db .update(profiles) .set({ plan: (plan ?? "starter") as typeof profiles.$inferSelect.plan, stripe_subscription_id: subscription.id, subscription_status: subscription.status, plan_expires_at: (subscription as any).current_period_end ? new Date((subscription as any).current_period_end * 1000).toISOString() : null, }) .where(eq(profiles.id, userId)) break } case "customer.subscription.deleted": { const subscription = event.data.object as Stripe.Subscription const userId = subscription.metadata?.supabase_user_id if (!userId) break await db .update(profiles) .set({ plan: "starter", stripe_subscription_id: null, subscription_status: "canceled", plan_expires_at: null, }) .where(eq(profiles.id, userId)) break } case "invoice.payment_failed": { const invoice = event.data.object as Stripe.Invoice const customerId = invoice.customer as string await db .update(profiles) .set({ subscription_status: "past_due" }) .where(eq(profiles.stripe_customer_id, customerId)) break } // Rent payment completed via payment link case "payment_intent.succeeded": { const intent = event.data.object as Stripe.PaymentIntent if (intent.metadata?.type !== "rent_payment") break const paymentId = intent.metadata?.payment_id if (paymentId) { await db .update(rent_payments) .set({ status: "paid", paid_date: new Date().toISOString().slice(0, 10), stripe_payment_intent_id: intent.id, }) .where(eq(rent_payments.id, paymentId)) } break } } return NextResponse.json({ received: true }) }