Files
songs2vid/app/api/account/subscribe/route.ts
T
Atakan Doğan Özban 9404efd86c Align preview typography with FFmpeg output, add video/song title split, and ship OSS updates.
Separate YouTube video titles from on-video song/artist fields with Pro gating, serve curated fonts and watermark assets for 1:1 preview parity, and include billing/API/docs/deploy stack for self-hosted release.
2026-07-27 16:26:19 +02:00

88 lines
2.7 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { activateProPlan } from "@/lib/billing";
import { PRO_PRICE_CENTS } from "@/lib/credits";
import { prisma } from "@/lib/db";
import { getSessionUser } from "@/lib/session";
import {
checkoutTaxAndReferenceOptions,
ensureStripeCustomer,
priceDataTaxFields,
productDataWithTaxCode,
} from "@/lib/stripe-checkout";
import { getStripe, isBillingDevMock, isStripeConfigured } from "@/lib/stripe";
/** Start Pro subscription Checkout (€5/mo) or mock-upgrade in development. */
export async function POST(req: NextRequest) {
const user = await getSessionUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (user.plan === "PREMIUM") {
return NextResponse.json({ error: "Already on Pro." }, { status: 400 });
}
const dbUser = await prisma.user.findUniqueOrThrow({
where: { id: user.id },
select: { email: true, stripeCustomerId: true },
});
if (isBillingDevMock()) {
await activateProPlan(user.id, {
stripeCustomerId: dbUser.stripeCustomerId,
stripeSubscriptionId: `dev_sub_${Date.now()}`,
cardLast4: "4242",
});
return NextResponse.json({ mocked: true, plan: "PREMIUM" });
}
if (!isStripeConfigured()) {
return NextResponse.json(
{ error: "Payments are not configured yet. Set STRIPE_SECRET_KEY." },
{ status: 503 },
);
}
const origin = process.env.NEXTAUTH_URL || req.nextUrl.origin;
const stripe = getStripe();
const customerId = await ensureStripeCustomer(user.id, dbUser.email);
const priceId = process.env.STRIPE_PRO_PRICE_ID;
const session = await stripe.checkout.sessions.create({
mode: "subscription",
customer: customerId,
line_items: priceId
? [{ price: priceId, quantity: 1 }]
: [
{
quantity: 1,
price_data: {
currency: "eur",
unit_amount: PRO_PRICE_CENTS,
recurring: { interval: "month" },
...priceDataTaxFields(),
product_data: productDataWithTaxCode(
"Songs2VID Pro",
"50 video credits / month · 1080p · API access · Includes Custom Branding, Watermark Positioning, and Bulk Image Matching",
),
},
},
],
metadata: {
type: "pro_subscription",
userId: user.id,
},
subscription_data: {
metadata: {
type: "pro_subscription",
userId: user.id,
},
},
...checkoutTaxAndReferenceOptions(user.id),
success_url: `${origin}/dashboard/settings?upgrade=success`,
cancel_url: `${origin}/dashboard/settings?upgrade=cancelled`,
});
return NextResponse.json({ url: session.url, sessionId: session.id });
}