40 lines
1.6 KiB
TypeScript
40 lines
1.6 KiB
TypeScript
import { z } from 'zod';
|
|
import { db } from '../../utils/db';
|
|
import { getCurrentUser } from '../../utils/auth';
|
|
import { getPayments } from '../../utils/payments';
|
|
import { recordAudit } from '../../utils/audit';
|
|
|
|
const schema = z.object({ plan: z.enum(['free', 'pro']) });
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await getCurrentUser(event);
|
|
if (!user) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
|
|
|
const parsed = schema.safeParse(await readBody(event).catch(() => ({})));
|
|
if (!parsed.success) throw createError({ statusCode: 400, statusMessage: 'Invalid plan' });
|
|
|
|
const config = useRuntimeConfig(event);
|
|
const siteUrl = String(config.public.siteUrl).replace(/\/$/, '');
|
|
const provider = getPayments(event);
|
|
const result = await provider.createCheckout({
|
|
userId: user.id,
|
|
email: user.email,
|
|
plan: parsed.data.plan,
|
|
successUrl: `${siteUrl}/dashboard`,
|
|
cancelUrl: `${siteUrl}/pricing`,
|
|
});
|
|
|
|
// The mock provider has no external redirect: activate the subscription right away.
|
|
if (provider.name === 'mock') {
|
|
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
await db.subscription.upsert({
|
|
where: { userId: user.id },
|
|
update: { plan: parsed.data.plan, status: 'active', provider: 'mock', currentPeriodEnd },
|
|
create: { userId: user.id, plan: parsed.data.plan, status: 'active', provider: 'mock', currentPeriodEnd },
|
|
});
|
|
}
|
|
|
|
await recordAudit({ actorId: user.id, action: 'billing.checkout', target: parsed.data.plan, meta: { provider: provider.name } });
|
|
return { id: result.id, url: result.url, provider: provider.name };
|
|
});
|