Prood
ApplicationsStorefront

Cart & 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}
Apply couponPOST /api/commerce/cart/couponPOST /v1/carts/{id}/coupon
Remove couponDELETE /api/commerce/cart/couponDELETE /v1/carts/{id}/coupon

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/actions.tsstartCheckout():

  1. PUT shipping and billing addresses on the cart via the Commerce API
  2. Select the first available shipping method
  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.

body: JSON.stringify({
  orderId: order.id,
  customerId: order.customerId,
  amount: priceToMajorAmount(order.totals.total),
  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 /success/{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 foundBFF creates new cart, retries operation
Out of stockAPI returns 409; client shows toast
Invalid couponAPI returns 400; form shows error message
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

On this page