templates2.Saas.Nuxt/server/api/billing/webhook.post.ts
w4c-templates d447340474
Some checks failed
CI / quality (push) Has been cancelled
chore(template): publish Saas.Nuxt
2026-10-11 23:25:14 +03:00

33 lines
1.5 KiB
TypeScript

import { db } from '../../utils/db';
import { getPayments } from '../../utils/payments';
import { recordAudit } from '../../utils/audit';
export default defineEventHandler(async (event) => {
const raw = await readRawBody(event, 'utf8');
const signature = getHeader(event, 'stripe-signature') ?? null;
const provider = getPayments(event);
let parsed;
try {
parsed = await provider.parseWebhook(raw ?? '', signature);
} catch (e) {
throw createError({ statusCode: 400, statusMessage: e instanceof Error ? e.message : 'Bad webhook' });
}
if (parsed.type === 'checkout.completed' && parsed.userId) {
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
await db.subscription.upsert({
where: { userId: parsed.userId },
update: { plan: parsed.plan ?? 'pro', status: 'active', provider: provider.name, currentPeriodEnd },
create: { userId: parsed.userId, plan: parsed.plan ?? 'pro', status: 'active', provider: provider.name, currentPeriodEnd },
});
await recordAudit({ actorId: parsed.userId, action: 'billing.activated', target: parsed.plan ?? 'pro' });
} else if (parsed.type === 'subscription.canceled' && parsed.userId) {
await db.subscription.update({ where: { userId: parsed.userId }, data: { status: 'canceled' } }).catch(() => undefined);
await recordAudit({ actorId: parsed.userId, action: 'billing.canceled', target: parsed.userId });
}
// Idempotent: re-delivered events re-apply the same upsert.
return { received: true };
});