Alt description missing in image
Beta: Plugins coming soon!
Alt description missing in image

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:

packages/@payments-driver/index.ts
export const startCheckout = paymentsDriver['startCheckout']

Where the underlying startCheckout implementation typically:

  • Calls ensureCustomer() internally to ensure a customer exists for the user
  • Maps lineItems to 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).