ApplicationsStorefront

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

Defined in lib/cart-cookie.ts:

PropertyValue
Namecommerce_cart_id
HTTP-onlyYes
Max-Age30 days
SameSiteLax
SecureYes (production)

The cookie stores only the cart ID — not cart contents. Contents are always fetched from the API.

3. Cart operations

ActionBFF routeAPI endpoint
Read cartGET /api/commerce/cartGET /v1/carts/{id}
Add itemPOST /api/commerce/cart/itemsPOST /v1/carts/{id}/items
Update quantityPATCH /api/commerce/cart/items/{itemId}PATCH /v1/carts/{id}/items/{itemId}
Remove itemDELETE /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():

  1. PUT shipping and billing addresses on the cart via the Commerce API
  2. 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
  3. POST /v1/carts/{id}/place-order — creates order with internal customerId (guest UUID or logged-in buyer)
  4. POST checkout app /api/sessions with orderId, customerId, amount, tenantId, and returnUrl (no email in Redis metadata)
  5. Redirect client to checkoutUrl from the session response

Email for payment (Stripe receipt) is collected at the checkout app pay boundary, not stored in the Redis session snapshot.

ts
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:

  1. Checkout app redirects to /confirm/{sessionId}
  2. Success page redirects to storefront returnUrl (/order-confirmation?order={id})
  3. Order confirmation page fetches order details from API

Error handling

ErrorHandling
Cart not found when adding an itemBFF creates a new cart and retries; any other refusal leaves the cart as it was
Out of stock, or more than is leftAPI returns 422; client shows toast
Checkout session creation failsServer action throws; checkout page shows error
Payment failedCheckout 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