import Stripe from "stripe"; import { prisma } from "@/lib/db"; import { stripePriceId, type BillingInterval } from "./catalog"; import type { PlanKey } from "./plans"; let client: Stripe | null = null; export function stripe(): Stripe { if (!client) { const key = process.env.STRIPE_SECRET_KEY; if (!key) throw new Error("STRIPE_SECRET_KEY is not set"); client = new Stripe(key); } return client; } export function isStripeConfigured(): boolean { return !!process.env.STRIPE_SECRET_KEY; } const appUrl = () => process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; /** Find-or-create the Stripe customer for a user and persist the id. */ export async function ensureStripeCustomer(user: { id: string; email: string; name: string; stripeCustomerId: string | null; }): Promise { if (user.stripeCustomerId) return user.stripeCustomerId; const customer = await stripe().customers.create({ email: user.email, name: user.name, metadata: { userId: user.id }, }); await prisma.user.update({ where: { id: user.id }, data: { stripeCustomerId: customer.id } }); return customer.id; } /** Create a Stripe Checkout Session for a subscription; returns the redirect URL. */ export async function createStripeCheckout(args: { user: { id: string; email: string; name: string; stripeCustomerId: string | null }; plan: PlanKey; interval: BillingInterval; subjectId: string; subjectType: "user" | "organization"; }): Promise { const price = stripePriceId(args.plan, args.interval); if (!price) throw new Error(`No Stripe price configured for ${args.plan}/${args.interval}`); const customer = await ensureStripeCustomer(args.user); const session = await stripe().checkout.sessions.create({ mode: "subscription", customer, line_items: [{ price, quantity: 1 }], client_reference_id: args.subjectId, metadata: { plan: args.plan, subjectId: args.subjectId, subjectType: args.subjectType }, subscription_data: { metadata: { plan: args.plan, subjectId: args.subjectId, subjectType: args.subjectType }, }, allow_promotion_codes: true, success_url: `${appUrl()}/billing?status=success`, cancel_url: `${appUrl()}/billing?status=cancel`, }); if (!session.url) throw new Error("Stripe did not return a checkout URL"); return session.url; } /** Create a Stripe Billing Portal session for managing/cancelling a subscription. */ export async function createStripePortal(customerId: string): Promise { const session = await stripe().billingPortal.sessions.create({ customer: customerId, return_url: `${appUrl()}/billing`, }); return session.url; }