72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import type Stripe from "stripe";
|
|
import { STRIPE_PRODUCT_TAX_CODE } from "@/lib/credits";
|
|
import { prisma } from "@/lib/db";
|
|
import { getStripe } from "@/lib/stripe";
|
|
|
|
/** Enable Stripe Tax only when Dashboard registrations are active (STRIPE_AUTOMATIC_TAX=true). */
|
|
export function isStripeAutomaticTaxEnabled(): boolean {
|
|
return process.env.STRIPE_AUTOMATIC_TAX === "true";
|
|
}
|
|
|
|
/**
|
|
* Shared Checkout Session options required/recommended by Stripe for SaaS:
|
|
* - client_reference_id for linking
|
|
* - automatic_tax (opt-in) + customer_update.address when tax is on
|
|
* - tax_code already set on product_data by callers
|
|
*/
|
|
export function checkoutTaxAndReferenceOptions(userId: string): Partial<Stripe.Checkout.SessionCreateParams> {
|
|
const opts: Partial<Stripe.Checkout.SessionCreateParams> = {
|
|
client_reference_id: userId,
|
|
};
|
|
|
|
if (isStripeAutomaticTaxEnabled()) {
|
|
opts.automatic_tax = { enabled: true };
|
|
opts.customer_update = { address: "auto", name: "auto" };
|
|
// Collect billing address so tax can be calculated for new/returning customers
|
|
opts.billing_address_collection = "required";
|
|
}
|
|
|
|
return opts;
|
|
}
|
|
|
|
/** tax_behavior required on Prices when automatic tax is enabled. */
|
|
export function priceDataTaxFields(): { tax_behavior?: Stripe.Checkout.SessionCreateParams.LineItem.PriceData["tax_behavior"] } {
|
|
if (!isStripeAutomaticTaxEnabled()) return {};
|
|
// Exclusive: listed prices are pre-tax; VAT/GST added at checkout
|
|
return { tax_behavior: "exclusive" };
|
|
}
|
|
|
|
export function productDataWithTaxCode(
|
|
name: string,
|
|
description: string,
|
|
): Stripe.Checkout.SessionCreateParams.LineItem.PriceData.ProductData {
|
|
return {
|
|
name,
|
|
description,
|
|
tax_code: STRIPE_PRODUCT_TAX_CODE,
|
|
};
|
|
}
|
|
|
|
/** Ensure the app user has a Stripe Customer and return its id. */
|
|
export async function ensureStripeCustomer(userId: string, email: string): Promise<string> {
|
|
const dbUser = await prisma.user.findUniqueOrThrow({
|
|
where: { id: userId },
|
|
select: { stripeCustomerId: true },
|
|
});
|
|
|
|
if (dbUser.stripeCustomerId) return dbUser.stripeCustomerId;
|
|
|
|
const stripe = getStripe();
|
|
const customer = await stripe.customers.create({
|
|
email,
|
|
metadata: { userId },
|
|
});
|
|
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { stripeCustomerId: customer.id },
|
|
});
|
|
|
|
return customer.id;
|
|
}
|