CheckoutSession
The CheckoutSession class orchestrates the complete checkout flow from customer info through payment and confirmation. It enforces valid state transitions, emits events, and works on any JavaScript runtime. The flow dynamically adapts based on the channel and fulfillment type.
Constructor
new CheckoutSession(config: CheckoutSessionConfig)
CheckoutSessionConfig
| Property | Type | Required | Description |
|---|---|---|---|
paymentProvider | PaymentProvider | Yes | Payment gateway instance |
amount | number | Yes | Order total amount |
currency | string | Yes | ISO currency code |
channel | CheckoutChannel | No | Sales channel (default: 'web') |
fulfillment | CheckoutFulfillment | No | Fulfillment type (smart default from channel) |
expiresIn | number | No | Session TTL in milliseconds |
orderId | string | No | External order reference |
returnUrl | string | No | 3DS redirect return URL |
cancelUrl | string | No | Payment cancellation URL |
webhookUrl | string | No | Per-transaction webhook URL |
CheckoutChannel
type CheckoutChannel = 'web' | 'pos' | 'agent' | 'link'
CheckoutFulfillment
type CheckoutFulfillment = 'shipping' | 'local_delivery' | 'pickup' | 'none'
Smart Defaults
| Channel | Default Fulfillment |
|---|---|
web | shipping |
pos | none |
agent | none |
link | none |
Explicit fulfillment always overrides the channel default.
Properties
All properties are read-only getters:
| Property | Type | Description |
|---|---|---|
state | CheckoutState | Current state machine state |
channel | CheckoutChannel | Resolved sales channel |
fulfillment | CheckoutFulfillment | Resolved fulfillment type |
customerInfo | CheckoutCustomerInfo | null | Customer details |
shippingAddress | Address | null | Shipping address |
billingAddress | Address | null | Billing address |
shippingMethodId | string | null | Selected shipping method |
paymentSession | PaymentSession | null | Current payment session |
amount | number | Order amount |
currency | string | Currency code |
orderId | string | null | Order ID |
error | Error | null | Last error |
Methods
setCustomerInfo(info)
setCustomerInfo(info: CheckoutCustomerInfo): void
Set customer details. Transitions from idle to info.
CheckoutCustomerInfo:
| Field | Type | Required |
|---|---|---|
email | string | Yes |
firstName | string | Yes |
lastName | string | Yes |
phone | string | No |
setShippingAddress(address, billingAddress?)
setShippingAddress(
address: Omit<Address, 'id' | 'isDefault'>,
billingAddress?: Omit<Address, 'id' | 'isDefault'>
): void
Set shipping address. Transitions from info to shipping. If billingAddress is omitted, the shipping address is used for billing.
shipping or local_delivery. For pickup or none, skip this step and call submitPayment() directly.setShippingMethod(methodId)
setShippingMethod(methodId: string): void
Set the shipping method. Does not trigger a state transition. Must be in shipping state.
setAmount(amount)
setAmount(amount: number): void
Update the order amount. Can be called before payment is submitted.
setOrderId(orderId)
setOrderId(orderId: string): void
Set the order ID if not provided at construction time.
submitPayment(options?)
submitPayment(options?: {
sourceToken?: string
idempotencyKey?: string
metadata?: Record<string, unknown>
}): Promise<PaymentSession>
Create a payment session with the provider. Valid source states:
shippingorfailed— always validinfo— valid when fulfillment ispickupornone
Returns a PaymentSession with an optional redirectUrl for 3DS.
confirmPayment(sessionId?)
confirmPayment(sessionId?: string): Promise<PaymentSession>
Confirm payment after 3DS redirect. Must be in payment state. Transitions to complete or failed.
handleWebhookUpdate(paymentSession)
handleWebhookUpdate(paymentSession: PaymentSession): void
Handle an async webhook update. Works from any non-terminal state. Transitions directly to complete (for captured) or failed (for failed/cancelled).
toSnapshot()
toSnapshot(): CheckoutSnapshot
Get a serializable snapshot of the current session state.
CheckoutSnapshot:
interface CheckoutSnapshot {
state: CheckoutState
channel: CheckoutChannel
fulfillment: CheckoutFulfillment
expiresAt: string | null
customerInfo: CheckoutCustomerInfo | null
shippingAddress: Omit<Address, 'id' | 'isDefault'> | null
billingAddress: Omit<Address, 'id' | 'isDefault'> | null
shippingMethodId: string | null
paymentSession: PaymentSession | null
amount: number
currency: string
orderId: string | null
error: string | null
}
Events
The session extends EventEmitter and emits these events:
| Event | Payload | When |
|---|---|---|
stateChange | { from: CheckoutState, to: CheckoutState } | Any state transition |
complete | { paymentSession: PaymentSession } | Payment captured |
error | { error: Error, state: CheckoutState } | Payment failed |
expired | {} | Session TTL exceeded |
session.on('stateChange', ({ from, to }) => {
console.log(`${from} → ${to}`)
})
State Transitions
type CheckoutState =
| 'idle'
| 'info'
| 'shipping'
| 'payment'
| 'confirming'
| 'complete'
| 'failed'
With Address (shipping, local_delivery)
| From | To | Method |
|---|---|---|
idle | info | setCustomerInfo() |
info | shipping | setShippingAddress() |
shipping | payment | submitPayment() |
payment | confirming | confirmPayment() |
confirming | complete | Payment captured |
confirming | failed | Payment declined |
failed | payment | submitPayment() (retry) |
Without Address (pickup, none)
| From | To | Method |
|---|---|---|
idle | info | setCustomerInfo() |
info | payment | submitPayment() |
payment | confirming | confirmPayment() |
confirming | complete | Payment captured |
confirming | failed | Payment declined |
failed | payment | submitPayment() (retry) |
Invalid transitions throw Error: Invalid transition: "{from}" → "{to}".
Session Expiry
When expiresIn is set, the session stores an expiresAt timestamp. After expiry:
- All state-mutating methods throw
Error: Checkout session has expired - The
expiredevent is emitted - The
expiresAtfield appears in the snapshot as an ISO string
const session = new CheckoutSession({
paymentProvider: tap,
amount: 45.00,
currency: 'SAR',
channel: 'pos',
expiresIn: 30 * 60 * 1000, // 30 minutes
})
session.on('expired', () => {
// Show "link expired" UI
})