GuidesRecipesStore credit

Recipe: store credit

Goal: show a signed-in shopper their store-credit balance and let them spend it at checkout.

Audience: storefront (server-side, via the storefront SDK).

Store credit is a per-shopper ledger held by the cash module — money attributed to an account, distinct from bearer-code gift cards. It is backed by the platform’s double-entry ledger, so a spend that would overdraw is rejected before any row is written.

Prerequisites

  • The store’s manifest gate storeCredit.enabled must be on. It ships disabled (a new money surface goes out dark), distinct from the giftCards.enabled gate.
  • Store credit applies to authenticated shoppers only — a balance is read for a signed-in session, not a guest. See Accounts & orders.

Step 1 — read the balance (honestly)

// Server-side, with the shopper's session established.
const balance = await store.storeCredit.balance();

Render the store-credit affordance only when the gate is on and you read a positive, known balance. Critically, distinguish a real zero from a failed read — a failed balance read is unknown, never “$0 credit”:

try {
  const balance = await store.storeCredit.balance();
  if (balance.amount_cents > 0) {
    return <StoreCreditOption balance={balance} />; // offer it
  }
  return null;                                       // genuinely no credit
} catch {
  return null;                                       // unknown — do NOT show "$0 credit"
}

This mirrors the honest-absence rule the platform holds everywhere: unknown and absent are different types (see Money invariants).

Step 2 — apply it at checkout

There is no separate “spend credit” call — it is a checkout tender. Pass apply_store_credit: true to store.checkout.submit:

// Server-side.
const result = await store.checkout.submit(
  {
    customer_ref: shopperRef,          // resolved from the session, never the body
    items: [{ variant_id: "var_123", quantity: 1 }],
    apply_store_credit: true,
    card: { ccnumber: "4111111111111111", ccexp: "12/28", cvv: "123" },
  },
  { idempotencyKey: `cart-${cartId}:attempt-1` },
);

The platform re-checks the real balance server-side and applies min(balance, orderTotal) as a partial tender — the amount charged to the card is reduced accordingly, and it never over-applies from a stale client number. If credit covers the whole total, no card is charged. The cash ledger is debited via cash’s deterministic-idempotency spend, so a retried checkout never double-spends.

The placed-order response reports the split:

{
  "outcome": "placed",
  "order": { "id": "ord_…", "total_cents": 4900 },
  "store_credit_applied_cents": 4900,
  "card_charged_cents": 0,
  // …
}

Multi-tender orders

An order can split across store credit, a gift card, and a payment card, in that priority. Each tender books independently against the ledger — commerce recognizes the full GMV while cash and giftcards each book their own liability draw-down, as separate balanced transactions. You do not orchestrate this; you pass the tenders and read the split back. See Checkout for the full outcome shape.

Where credit comes from

This recipe covers spending credit. Store credit is granted through the back-office — for example, a merchant issuing credit, or a return/refund returning value as store credit rather than to a card. Grants and spends both post through cash’s deterministic-idempotency discipline, and a debit that would overdraw is rejected up front, never caught and reversed after the fact.