Alt description missing in image
Beta: Plugins coming soon!
@payments/striperesolversstartStripeCheckout
Alt description missing in image

startStripeCheckout()

Creates a Stripe Checkout Session for the given user and lineItems. Ensures a Stripe Customer exists first, then returns the hosted checkoutUrl.

Usage - Driver

import { startCheckout } from '@payments/driver'

If you have ‘stripe’ set up as the default payment driver in @app/config ofcourse.

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)

Usage - Directly

import { startStripeCheckout } from '@payments/stripe/resolvers/startStripeCheckout.resolver'
const { checkoutUrl } = await startStripeCheckout({
    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

  • Calls ensureStripeCustomer() to get { user, customer }.
  • Maps lineItems to Stripe line_items format and merges coupon/promo codes.
  • Enables automatic tax collection when requested.
  • Optionally sets locale and / or subscription trial days if passed.
  • Returns the hosted session URL for you to redirect to.
startStripeCheckout.resolver.ts
export const startStripeCheckout = createResolver(async ({ args, parseArgs, formatOutput }) => {
 
    // Args
    const { mode, lineItems, successUrl, cancelUrl, metadata, customerOverrides } = parseArgs(args)
 
    // Ensure there is a customer first
    const { user, customer } = await ensureStripeCustomer({ user: args.user, metadata, customerOverrides })
 
    // Normalize lineItems & discounts from driver format to stripe format
    const stripeLineItems = lineItems.map(item => ({
        price: item.priceVariantId,
        quantity: item.quantity || 1,
    }))
    const stripeDiscounts = [
        ...(args.coupons ? args.coupons.map(code => ({ coupon: code })) : []),
        ...(args.promoCodes ? args.promoCodes.map(code => ({ promotion_code: code })) : []),
    ]
 
    // Start session
    const session = await createCustomerCheckoutSession({
 
        // Recommended: Always pass a customerId (why we call ensureCustomer() first)
        customerId: customer.customerId,
        customer: customer.customerId,
 
        // Main config
        mode, // e.g. 'subsription' | 'checkout'
        lineItems: stripeLineItems,
        discounts: stripeDiscounts,
 
        // language & taxes?
        locale: args.locale || 'auto',
        automatic_tax: { enabled: args.autoCalculateTax || false },
 
        // trial for subscriptions?
        ...((args.trialDays && mode === 'subscription') ? { subscription_data: { trial_period_days: args.trialDays } } : {}),
        
        // Recommended: Always add at least userId here
        metadata: { userId: user.userId, ...(metadata || {}) },
 
        // Recommended: 
        successUrl,
        cancelUrl,
    })
 
 
    return formatOutput({ provider: 'stripe', user, customer, checkoutUrl: session.url! })
})

StartCheckoutInput

Follows 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

Follows 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?