44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { z } from 'zod';
|
|
import { getCurrentUser } from '@/lib/server/auth';
|
|
import { db } from '@/lib/server/db';
|
|
import { getPayments } from '@/lib/server/payments';
|
|
import { recordAudit } from '@/lib/server/audit';
|
|
import { env } from '@/lib/env';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
const schema = z.object({ plan: z.enum(['free', 'pro']) });
|
|
|
|
export async function POST(request: Request) {
|
|
const user = await getCurrentUser();
|
|
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
const parsed = schema.safeParse(await request.json().catch(() => ({})));
|
|
if (!parsed.success) return NextResponse.json({ error: 'Invalid plan' }, { status: 400 });
|
|
|
|
const provider = getPayments();
|
|
const siteUrl = env.NEXT_PUBLIC_SITE_URL.replace(/\/$/, '');
|
|
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 so the
|
|
// full "plan → checkout → return → active" flow works with zero configuration.
|
|
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 NextResponse.json({ id: result.id, url: result.url, provider: provider.name });
|
|
}
|