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 balance attributed to an account, distinct from bearer-code gift cards. A spend that would overdraw the balance is rejected up front, before anything is charged.

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 balance is drawn down with deterministic idempotency, 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 is applied independently — store credit and the gift card each draw down their own balance while the payment card is charged the remainder. 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 apply with deterministic idempotency, and a debit that would overdraw the balance is rejected up front, never caught and reversed after the fact.