Checkouts API
A checkout is the heavyweight, TTL'd transactional session. It freezes a cart's line items, walks the FSM (address → shipping → payment → processing → completed), and on success creates an order.
State machine
pending created from cart, no address yet
│
address email + shipping_address set; billing optional
│
shipping method + rate (+ optional pickup_point) chosen
│
payment payment method picked, totals frozen
│
processing start_payment called, awaiting confirm
│
├──▶ completed order created, inventory committed, email queued
└──▶ failed payment declined; can retry → back to payment
abandoned TTL expired (default 60min); reservations released
Phase progresses on each successful PATCH. Reverse jumps allowed only
failed → payment for retry.
Create from cart
curl -X POST "$SF/checkouts.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{"cart_token": "8H3xa9...", "email": "[email protected]"}'
This atomically:
- Validates the cart is active and non-empty
- Snapshots all line items into
checkout_line_itemswith frozen prices + tax rates - Reserves inventory at the highest-priority active location (per
commerce.checkout.reserve_at) - Recomputes totals
- Sets
expires_at = now() + commerce.checkout.session_ttl_minutes(default 60)
Returns the checkout with its own token.
Set address
curl -X PUT "$SF/checkouts/{token}/address.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"shipping_address": {
"first_name": "Anna",
"last_name": "Nováková",
"address1": "Plzeňská 8",
"city": "Praha",
"country_code": "CZ",
"zip": "15000",
"phone": "+420..."
},
"billing_address": null,
"same_as_shipping": true,
"customer_note": "Prosím nezvonit, dítě spí."
}'
Validates that both addresses (or just shipping if same_as_shipping=true)
have address1 + city + country_code. Phase advances:
pending → address.
Quote shipping
curl "$SF/checkouts/{token}/shipping_quote.json" \
-H "X-Storefront-Key: $SK"
Computed against the checkout's destination + line weights + subtotal
- currency. See Shipping for the engine.
Set shipping
curl -X PUT "$SF/checkouts/{token}/shipping.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{
"shipping_method_id": 308...,
"shipping_rate_id": 308...,
"pickup_point_id": null
}'
Validates currency matches, pickup-point requirement enforced, etc.
Recomputes totals (shipping_cents + shipping VAT).
Phase: address → shipping.
Apply discount code
curl -X POST "$SF/checkouts/{token}/discount.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{"code":"SAVE10"}'
See Discounts for the validation rules. Stack multiple stackable codes; non-stackable codes replace. Czech VAT math: tax computed on post-discount totals.
Remove with DELETE /checkouts/{token}/discount/{code}.json.
B2B VAT validation
curl -X PUT "$SF/checkouts/{token}/vat.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{"vat_country_code":"DE","vat_number":"123456789"}'
Triggers VIES validation (with cache) and applies reverse charge if eligible. See Tax.
Set payment method
curl -X PUT "$SF/checkouts/{token}/payment_method.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{"payment_method":"cod"}'
Method must match a registered connector (currently cod and
bank_transfer; see Payments). Phase:
shipping → payment.
Start payment
curl -X POST "$SF/checkouts/{token}/start_payment.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{
"return_url": "https://shop.example.com/success",
"cancel_url": "https://shop.example.com/cancel"
}'
Calls the connector's start_payment. For card gateways, returns a
redirect_url. For COD/bank, immediately ready to confirm.
Creates a checkout_payment_attempts row.
Phase: payment → processing.
Confirm
After the gateway redirects back (or for COD/bank, immediately):
curl -X POST "$SF/checkouts/{token}/confirm.json" \
-H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
-d '{"callback_payload": {"gateway_token":"..."}}'
Connector decides success or failure. On failure, phase moves to
failed and the customer can retry with a different method. On
success, the checkout is ready to complete (but not yet completed
— the storefront calls complete separately).
Complete
curl -X POST "$SF/checkouts/{token}/complete.json" \
-H "X-Storefront-Key: $SK" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'
Atomic in one DB transaction:
- Resolves customer (creates as
guestif not seen before) - Calls
orders.create_order_from_checkout— fullOrderrow with frozen line items, addresses, shipping snapshot, discount codes, totals - Converts inventory reservations:
reserved → committed - Records
discount_redemptionsfor usage tracking - Marks the cart
status=converted - Sets
checkout.phase = completed,order_idpopulated - Publishes
checkout.completedevent - Inserts an
outbox_dispatchesrow fororder.confirmationemail
The Idempotency-Key header is required (Q9 design decision). Re-calling
complete with the same key returns the same checkout — no duplicate
order, no duplicate email.
Endpoints
POST /storefront/2026-01/checkouts.json
GET /storefront/2026-01/checkouts/{token}.json
PUT /storefront/2026-01/checkouts/{token}/address.json
GET /storefront/2026-01/checkouts/{token}/shipping_quote.json
PUT /storefront/2026-01/checkouts/{token}/shipping.json
POST /storefront/2026-01/checkouts/{token}/discount.json
DELETE /storefront/2026-01/checkouts/{token}/discount/{code}.json
PUT /storefront/2026-01/checkouts/{token}/vat.json
PUT /storefront/2026-01/checkouts/{token}/payment_method.json
POST /storefront/2026-01/checkouts/{token}/start_payment.json
POST /storefront/2026-01/checkouts/{token}/confirm.json
POST /storefront/2026-01/checkouts/{token}/complete.json
Next: Payments.