Skip to content

Cart & sessions

Architecture

flowchart LR
  Browser --"fetch('/api/cart/add')"--> Route[Next.js Route Handler]
  Route --"POST with Cart-Token + Nonce"--> Woo[Woo Store API]
  Woo --"Set Cart-Token + Nonce"--> Route
  Route --"Set HttpOnly cookies"--> Browser
  Route -->|JSON body| Browser

The PWA never talks to Woo directly from the browser. Instead:

  1. fp_cart HttpOnly cookie holds the Woo Store API Cart-Token (session id)
  2. fp_nonce HttpOnly cookie holds the Woo REST nonce (CSRF)
  3. Every client call goes to /api/cart/* on our own origin — no CORS
  4. The route handler forwards the token+nonce to Woo, captures any refreshed values from the response, and writes them back to the cookie

Code lives in apps/web/lib/woo-proxy.ts and apps/web/app/api/cart/.

Client state

Zustand store at apps/web/stores/cart.ts:

useCart.getState()
// { cart, pending, error, refresh, add, update, remove }

The header's <CartCount /> component subscribes to the count and updates in real time.

API surface

Route Method Forwards to
/api/cart GET /wc/store/v1/cart
/api/cart/add POST /wc/store/v1/cart/add-item
/api/cart/update POST /wc/store/v1/cart/update-item
/api/cart/remove POST /wc/store/v1/cart/remove-item

Server-side cart reads

For the SSR cart page (app/cart/page.tsx), we can't use the route-handler-based proxy (cookies are writable only in route handlers). Instead readCart() uses a lighter variant that reads the cookie but skips writing back:

export async function readCart(): Promise<unknown | null> {
  const cartToken = (await cookies()).get("fp_cart")?.value;
  const res = await fetch(base() + "/cart", {
    headers: cartToken ? { "Cart-Token": cartToken } : {},
    cache: "no-store",
  });
  return res.ok ? res.json() : null;
}

Stale by design; the client hydrates via /api/cart on mount.

Gotcha: mutations need the nonce

Woo Store API returns 401 woocommerce_rest_missing_nonce if a mutation arrives without a Nonce header. The proxy auto-primes it by doing a GET /cart first if no nonce is stored yet — that GET returns both Cart-Token and Nonce headers.

Cart → order

When the user hits Checkout, the cart items are carried into Woo's native checkout via the same Cart-Token. The Stripe checkout UI (planned) is built with Stripe Elements, POSTs directly to Woo's /checkout endpoint with the payment method, and Woo creates the order + fires ShipStation webhook.