startCheckout()
import { startCheckout } from '@payments/driver'Creates a hosted checkout session for the given user and lineItems. The function delegates to your active payment driver implementation (e.g. Stripe, Polar) which handles provider-specific logic like ensuring a customer exists and creating the checkout session.
The driver automatically uses the payment provider you’ve selected in appConfig.ts.
const { checkoutUrl } = await startCheckout({
user,
lineItems: [{ priceVariantId: 'price_XXXX', quantity: 1 }],
mode: 'subscription',
successUrl: `${baseURL}/billing/success`,
cancelUrl: `${baseURL}/billing/cancel`,
trialDays: 14,
locale: 'auto',
autoCalculateTax: true,
coupons: ['WELCOME10'],
})
// redirect(checkoutUrl)Implementation details
Each payment driver implementation handles the provider-specific checkout creation:
export const startCheckout = paymentsDriver['startCheckout']Where the underlying startCheckout implementation typically:
- Calls
ensureCustomer()internally to ensure a customer exists for the user - Maps
lineItemsto the provider’s native format (e.g. Stripe line items, Polar products) - Applies coupons and promotion codes if provided
- Enables automatic tax collection when requested
- Optionally sets locale and subscription trial days
- Returns the hosted checkout URL for redirect
The driver entrypoint forwards calls to the active implementation from @app/registries/drivers/payments.drivers.generated.ts.
StartCheckoutInput
The underlying driver implementations will follow the driver signature StartCheckoutInput schema:
import { StartCheckoutInput } from '@payments/driver/driver.signature'export const StartCheckoutInput = schema('StartCheckoutInput', {
user: User,
lineItems: CheckoutLineItem.array().describe('Array of line items to include in the checkout session'),
mode: z.enum(['payment', 'subscription', 'setup']).describe('Whether this is a one-time purchase or a recurring subscription'),
successUrl: z.string().url().describe('URL to redirect the user to after successful checkout'),
cancelUrl: z.string().url().describe('URL to redirect the user to if they cancel the checkout'),
coupons: z.array(z.string()).nullish().describe('Array of coupon codes to apply to the checkout session'),
promoCodes: z.array(z.string()).nullish().describe('Array of promotion codes to apply to the checkout session'),
trialDays: z.number().int().min(0).nullish().describe('Number of trial days to apply for subscription checkouts'),
locale: z.string().nullish().describe('Locale/language code for the checkout session'),
autoCalculateTax: z.boolean().nullish().describe('Whether to automatically calculate tax for the checkout session'),
customerOverrides: z.object({
email: Customer.shape.email.nullish().describe('Override the email on the Customer record for this checkout session'),
name: Customer.shape.name.nullish().describe('Override the name on the Customer record for this checkout session'),
address: z.object({
line1: z.string().nullish().describe('Address line 1'),
city: z.string().nullish().describe('City'),
state: z.string().nullish().describe('State/Province'),
postalCode: z.string().nullish().describe('Postal/ZIP code'),
country: z.string().nullish().describe('Country code (ISO 3166-1 alpha-2)'),
}).nullish().describe('Override the address on the Customer record for this checkout session'),
taxId: z.string().nullish().describe('Override the tax ID on the Customer record for this checkout session'),
}).nullish().describe('Object containing fields to override on the Customer record for this checkout session'),
metadata: z.record(z.string(), z.any()).nullish().describe('Optional metadata to attach to the checkout session'),
})StartCheckoutOutput
The underlying driver implementations will follow the driver signature StartCheckoutOutput schema:
import { StartCheckoutOutput } from '@payments/driver/driver.signature'export const StartCheckoutOutput = schema('StartCheckoutOutput', {
provider: PaymentProvidable.shape.provider,
user: User,
customer: Customer,
checkoutUrl: z.string().url().describe('URL of the checkout session to redirect the user to'),
})Recommendations
It’s probably best to validate entitlement constraints (e.g., an existing subscription) before starting a new checkout. Should users be able to checkout multiple times? Is every item purchaseable multiple times? Or is there a limit where it makes more sense to redirect to either the dashboard or customer portal?
For implementation-specific details, see your provider’s docs (e.g. Stripe’s implementation).
