templates2.Store.Next/app/api/billing/checkout/route.ts
w4c-templates bb75e7c742
Some checks are pending
CI / quality (push) Waiting to run
chore(template): publish Store.Next
2026-10-11 23:24:13 +03:00

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 });
}