Cart and checkout
How the storefront manages cart state, places orders, and redirects to the checkout app.
The storefront cart uses a cookie-backed cart ID with BFF routes for client-side operations, and a server action to place orders and redirect to the checkout app.
Cart lifecycle
1. Cart creation
When a customer first adds an item:
Drag to pan · Scroll to zoom
2. Cart cookie
Defined in lib/cart-cookie.ts:
| Property | Value |
|---|---|
| Name | commerce_cart_id |
| HTTP-only | Yes |
| Max-Age | 30 days |
| SameSite | Lax |
| Secure | Yes (production) |
The cookie stores only the cart ID — not cart contents. Contents are always fetched from the API.
3. Cart operations
| Action | BFF route | API endpoint |
|---|---|---|
| Read cart | GET /api/commerce/cart | GET /v1/carts/{id} |
| Add item | POST /api/commerce/cart/items | POST /v1/carts/{id}/items |
| Update quantity | PATCH /api/commerce/cart/items/{itemId} | PATCH /v1/carts/{id}/items/{itemId} |
| Remove item | DELETE /api/commerce/cart/items/{itemId} | DELETE /v1/carts/{id}/items/{itemId} |
4. Client state
CartProvider (React context) maintains client-side cart state for instant UI updates. It syncs with BFF routes on mount and after mutations.
Checkout flow
Step 1 — Checkout page
The /checkout page renders CheckoutFlow which collects:
- Customer email, first name, last name, phone
- Shipping address (street, city, country, postal code)
- Optional billing address
- Shipping method selection (from API
GET /v1/carts/{id}/shipping-methods)
Step 2 — Place order (server action)
app/(checkout)/checkout/actions.ts → startCheckout():
- PUT shipping and billing addresses on the cart via the Commerce API
- Set the shipping method the shopper chose; a cart with items that ship cannot be ordered without an address and a method, and a cart without any skips that step
POST /v1/carts/{id}/place-order— creates order with internalcustomerId(guest UUID or logged-in buyer)POSTcheckout app/api/sessionswithorderId,customerId, amount,tenantId, andreturnUrl(no email in Redis metadata)- Redirect client to
checkoutUrlfrom the session response
Email for payment (Stripe receipt) is collected at the checkout app pay boundary, not stored in the Redis session snapshot.
body: JSON.stringify({
orderId: order.id,
customerId: order.customerId,
amount: order.totals.total.amount,
currency: order.totals.total.currency,
tenantId,
returnUrl: `${storefrontOrigin}/order-confirmation?orderId=${order.id}`,
providerId,
fulfillment: "none",
})Step 3 — Payment (checkout app)
The customer is redirected to apps/checkout/c/{sessionId}. See Checkout app for payment UI details.
Step 4 — Return to storefront
After successful payment:
- Checkout app redirects to
/confirm/{sessionId} - Success page redirects to storefront
returnUrl(/order-confirmation?order={id}) - Order confirmation page fetches order details from API
Error handling
| Error | Handling |
|---|---|
| Cart not found when adding an item | BFF creates a new cart and retries; any other refusal leaves the cart as it was |
| Out of stock, or more than is left | API returns 422; client shows toast |
| Checkout session creation fails | Server action throws; checkout page shows error |
| Payment failed | Checkout app shows retry UI; order stays pending |
Security considerations
- Cart cookie is HTTP-only — not accessible to JavaScript (XSS protection)
- Checkout session creation requires
x-checkout-secret— only the storefront can create sessions - Tenant ID is resolved server-side — never accepted from client input
- Order amounts are validated server-side by the API — not trusted from the client