import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' import { createHmac } from 'crypto' // Polar product IDs → tier flags const PRODUCT_TIERS: Record = { 'a291a3d9-fa82-4e88-ba89-c6005401398f': { isPremium: true, isPro: false }, // Premium '7f983017-e1f3-440e-be2a-5a5d19c9c7b0': { isPremium: true, isPro: true }, // Pro } function verifySignature(body: string, headers: Headers): boolean { const secret = process.env.POLAR_WEBHOOK_SECRET if (!secret) return false // Polar uses Svix: signature is over "id.timestamp.body" const webhookId = headers.get('webhook-id') const timestamp = headers.get('webhook-timestamp') const signature = headers.get('webhook-signature') if (!webhookId || !timestamp || !signature) return false const signedContent = `${webhookId}.${timestamp}.${body}` const secretBytes = Buffer.from(secret.replace('whsec_', ''), 'base64') const computed = createHmac('sha256', secretBytes).update(signedContent).digest('base64') // Signature header may contain multiple: "v1, v1," return signature.split(' ').some(s => s === `v1,${computed}`) } export async function POST(req: NextRequest) { const body = await req.text() if (process.env.POLAR_WEBHOOK_SECRET && !verifySignature(body, req.headers)) { return NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) } let event: { type: string; data: any } try { event = JSON.parse(body) } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) } const { type, data } = event try { switch (type) { case 'subscription.created': case 'subscription.updated': { const userId = data.metadata?.userId || data.customer?.metadata?.userId if (!userId) break const productId = data.product_id || data.product?.id const tier = productId ? PRODUCT_TIERS[productId] : null const isActive = ['active', 'trialing'].includes(data.status) if (isActive && tier) { await prisma.user.update({ where: { id: userId }, data: tier, }) } else if (!isActive) { await prisma.user.update({ where: { id: userId }, data: { isPremium: false, isPro: false }, }) } break } case 'subscription.revoked': case 'subscription.canceled': { const userId = data.metadata?.userId || data.customer?.metadata?.userId if (!userId) break await prisma.user.update({ where: { id: userId }, data: { isPremium: false, isPro: false }, }) break } } } catch (e) { console.error('Polar webhook handler error:', e) return NextResponse.json({ error: 'Handler error' }, { status: 500 }) } return NextResponse.json({ received: true }) }