Files

77 lines
2.6 KiB
TypeScript

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<string> {
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<string> {
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<string> {
const session = await stripe().billingPortal.sessions.create({
customer: customerId,
return_url: `${appUrl()}/billing`,
});
return session.url;
}