Documentation

Discounts and promo codes

Discount creation, codes, eligibility rules, automatic discounts, and cart-level calculation.

The discount module handles every kind of price reduction — single-use coupon codes, evergreen sale codes, automatic site-wide promotions, BOGO rules, free-shipping thresholds, group benefits. All flow through one calculation endpoint that runs against the cart and returns the line-by-line and total reduction.

Validate and calculate

POST/storefront/discounts/validateNo auth
POST/storefront/discounts/calculateNo auth
POST/storefront/discounts/automaticNo auth
POST/storefront/discounts/applyNo auth

validate checks whether a code exists, is active, and applies to the current cart — without committing usage. calculate accepts the cart, an optional code, and addresses, and returns subtotal, discount, shipping, tax, and total. This is the single endpoint a checkout UI calls on every cart change. automatic returns the discounts the cart qualifies for without a code — site-wide promos, group-tier discounts, free-shipping thresholds. apply records that a code was used (decrementing usage limits).

const result = await fetch('/api/storefront/discounts/calculate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', orgid: ORG_ID },
  body: JSON.stringify({
    cart: cartObject,
    code: 'SPRING20',
    shippingAddress: { country: 'US', state: 'CA', zip: '94110' },
    customerId: customerId,
  }),
}).then(r => r.json());

// {
//   subtotal: 200,
//   discount: 40,
//   shipping: 8.99,
//   tax: 14.4,
//   total: 183.39,
//   appliedDiscounts: [{ code: 'SPRING20', amount: 40, type: 'percentage' }]
// }
GET/storefront/discounts/promotions/activeNo auth

Returns the live promo banners — the discounts you'd display on a homepage strip ("Free shipping over $50"). No cart needed.

Discount CRUD

POST/storefront/discountsJWT
GET/storefront/discountsJWT
GET/storefront/discounts/:identifierJWT
PUT/storefront/discounts/:identifierJWT
DELETE/storefront/discounts/:identifierJWT
{
  "name": "Spring Sale 20%",
  "code": "SPRING20",
  "type": "percentage",
  "value": 20,
  "scope": "cart",
  "minSubtotal": 50,
  "maxRedemptions": 1000,
  "perCustomerLimit": 1,
  "startsAt": "2026-04-01T00:00:00Z",
  "endsAt": "2026-04-30T23:59:59Z",
  "stackable": false,
  "eligibility": {
    "tags": ["vip"],
    "categories": ["spring-collection"],
    "excludeCategories": ["clearance"]
  }
}

The type enum: percentage, fixed, free-shipping, bogo, bundle, tiered. scope is cart, line-item, or shipping. eligibility filters by customer tags, product categories, customer-segment IDs, or merchant-account flags.

Activation and lifecycle

PUT/storefront/discounts/:identifier/activateJWT
PUT/storefront/discounts/:identifier/deactivateJWT

A deactivated discount fails validate immediately with inactive. The record stays for historical reporting.

Usage tracking and reversal

POST/storefront/discounts/:identifier/usageJWT
POST/storefront/discounts/:identifier/reverseJWT

usage increments the redemption counter (called automatically by checkout). reverse decrements it — used when an order is cancelled before fulfillment so the customer's per-customer-limit doesn't burn the slot.

Statistics

GET/storefront/discounts/statsJWT

Per-discount counts: total redemptions, gross uplift, average order value with the discount, conversion rate. Useful for deciding whether a code is paying for itself.

Stacking rules

By default, discounts don't stack. Set stackable: true and the calculation engine combines compatible discounts (percentage + free-shipping is fine; two percentage codes is not, by default). The order is fixed: line-item discounts first, then cart-level percentages, then fixed amounts, then free-shipping.

// Two stackable discounts apply
{
  "appliedDiscounts": [
    { "code": "FREESHIP", "type": "free-shipping", "amount": 8.99 },
    { "code": "SPRING20", "type": "percentage", "amount": 40 }
  ]
}

To restrict combinations, use the mutuallyExclusiveWith: [<discountId>] array on the discount record.

Customer-specific codes

Generate single-use unique codes for individual customers via POST /storefront/discounts with oneTimeUse: true and assignedTo: <customerId>. The code only validates for that customer's cart and burns on first use. Use this for win-back emails, support gestures, and influencer attribution.

Storefront pricing always re-runs discounts/calculate server-side at checkout, regardless of what the client computed. The client preview is for UX only — never trust client-computed totals.

Eligibility against group benefits

If a customer is enrolled in a group/benefit programme via the client account (e.g. "students get 10% off"), the calculate endpoint folds the benefit into the same response. The benefit appears under appliedDiscounts with source: "benefit" so the UI can label it differently.