Documentation

Cart

Reading, updating, and persisting shopping carts for guests and signed-in customers.

A cart in AppEngine is a server-persisted document keyed by an authorid (the customer or guest identifier) and a cartid. There is one read endpoint, one write endpoint, and one clear endpoint. Line-item add/remove/update all go through the same update call — the client posts the desired cart state, the server replaces it.

Read a cart

GET/storefront/cart/get/:authorid/:cartid?No auth

authorid is the signed-in customer's SK when authenticated, or a stable guest id (UUID) stored in localStorage for anonymous shoppers. cartid is optional — omit it and the server returns the customer's most recent open cart.

// authenticated
const cart = await storefrontAPI.getCart(`${authorId}/${cartId}`);

// guest (no cartId yet — server creates one)
const guestCart = await fetch(
  `/api/storefront/cart/get/${guestId}`,
  { headers: { orgid: ORG_ID } },
).then(r => r.json());

The response is a BaseModel<Cart>. The interesting fields:

{
  "pk": "my-org|cart",
  "sk": "my-org|cart|cart-123",
  "data": {
    "cartId": "cart-123",
    "authorId": "cust-abc",
    "productItems": [
      {
        "sku": "SNK-AIR-1-9-BLK",
        "name": "Air Sneaker (Size 9 / Black)",
        "image": "https://cdn.../sneaker-1-black.jpg",
        "options": { "size": "9", "color": "black" },
        "quantity": 1,
        "price": 89.99
      }
    ],
    "rentalItems": [],
    "subtotal": 89.99,
    "currency": "USD",
    "updatedAt": "2026-04-25T10:12:00Z"
  }
}

Update a cart

POST/storefront/cart/update/:cartidNo auth

The body is the new cart state — line items and totals. The server reconciles prices and stock against the catalog. To remove an item, leave it out of productItems. To bump quantity, change quantity and re-post.

await fetch(`/api/storefront/cart/update/${cartId}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', orgid: ORG_ID },
  body: JSON.stringify({
    authorId: authorId,
    productItems: [
      { sku: 'SNK-AIR-1-9-BLK', quantity: 2, price: 89.99, name: 'Air Sneaker' },
    ],
    rentalItems: [],
  }),
});

The update endpoint is intentionally idempotent. The client owns the truth of "what's in the cart"; the server validates and persists. Don't try to model add-line / remove-line as separate calls.

Clear a cart

GET/storefront/cart/clear/:cartidNo auth

A GET (not DELETE) for symmetry with the rest of the cart endpoints. The cart record stays — only productItems and rentalItems are emptied — so the client can keep the same cartid after clearing.

Anonymous vs authenticated

The same endpoints work for both. The pattern in base-app:

  1. On first interaction, generate a UUID and store it in localStorage as guest-cart-id.
  2. Use that UUID as both authorid and cartid until the user signs in.
  3. After sign-in, call cart/update with the customer's SK as authorid and the same cartid — the cart attaches to the customer record.

Step 3 is deliberate: there's no separate "merge carts" endpoint. The customer's pre-signin cart is what they had as a guest, and any prior server-side cart for that customer is replaced on the next update. If you need merge semantics, do them client-side before the first authenticated update.

Mixed carts (products + rentals)

Rentals share the cart but live in rentalItems. The shape adds time fields:

{
  "rentalItems": [
    {
      "sku": "RENT-DRILL-01",
      "quantity": 1,
      "startDate": "2026-05-01T09:00:00Z",
      "endDate": "2026-05-03T09:00:00Z",
      "rentalPeriod": "daily",
      "price": 25
    }
  ]
}

When checkout fires, the server splits the cart: products become a regular order, rentals become rental bookings. See POST /storefront/checkout-mixed for that flow.

Pricing the cart

Don't compute totals in the client beyond a rough preview. The pricing engine knows about coupons, group benefits, and free-shipping minimums:

POST/storefront/pricing/calculate-cartNo auth
POST/storefront/discounts/calculateNo auth

The discounts/calculate endpoint is the single source of truth for "what does this cart cost right now" — it accepts the cart, an optional coupon code, and shipping/billing addresses, and returns subtotal, shipping, tax, discount, and total. Re-run it on every cart change and at checkout. The base-app Checkout component does exactly this.

Persistence rules

  • A cart record lives until explicitly cleared or until the org's purge policy kicks in (default 90 days untouched).
  • One customer can have multiple carts (saved-for-later patterns); only one is "active" at any time, picked by the most recent updatedAt.
  • Carts are scoped to the org via orgid like every other record. A cart from org A is invisible to org B even if the SK collides.