36 lines
1.6 KiB
TypeScript
36 lines
1.6 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { db } from '@/lib/server/db';
|
|
import { getPayments } from '@/lib/server/payments';
|
|
import { recordAudit } from '@/lib/server/audit';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export async function POST(request: Request) {
|
|
const raw = await request.text();
|
|
const signature = request.headers.get('stripe-signature');
|
|
const provider = getPayments();
|
|
|
|
let event;
|
|
try {
|
|
event = await provider.parseWebhook(raw, signature);
|
|
} catch (e) {
|
|
return NextResponse.json({ error: e instanceof Error ? e.message : 'Bad webhook' }, { status: 400 });
|
|
}
|
|
|
|
if (event.type === 'checkout.completed' && event.userId) {
|
|
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
await db.subscription.upsert({
|
|
where: { userId: event.userId },
|
|
update: { plan: event.plan ?? 'pro', status: 'active', provider: provider.name, currentPeriodEnd },
|
|
create: { userId: event.userId, plan: event.plan ?? 'pro', status: 'active', provider: provider.name, currentPeriodEnd },
|
|
});
|
|
await recordAudit({ actorId: event.userId, action: 'billing.activated', target: event.plan ?? 'pro' });
|
|
} else if (event.type === 'subscription.canceled' && event.userId) {
|
|
await db.subscription.update({ where: { userId: event.userId }, data: { status: 'canceled' } }).catch(() => undefined);
|
|
await recordAudit({ actorId: event.userId, action: 'billing.canceled', target: event.userId });
|
|
}
|
|
|
|
// Idempotent: re-delivered events simply re-apply the same upsert.
|
|
return NextResponse.json({ received: true });
|
|
}
|