Discounts and coupons
Goal: preview what a coupon discounts, and redeem it on an order.
Audience: a storefront developer applying a coupon code at checkout.
Coupons are operator/console-managed — you create and list them from the console, not through the API. From the storefront you only apply a code that already exists. This guide documents exactly what ships.
What a coupon is
A coupon is a tenant-scoped row: a code, a kind, a value, and an optional
redemption cap.
| Field | Meaning |
|---|---|
code | The redeemable string a shopper types. |
kind | "percent" or "fixed". |
value | For percent: points off the subtotal, 1..100. For fixed: integer cents off the subtotal. |
max_redemptions | The lifetime redemption cap, or null for unlimited. |
times_redeemed | How many placed orders have consumed it (read-only). |
active | Whether it can still be redeemed. |
A fixed discount is clamped so it can never exceed the subtotal — a
value: 10000 coupon on a $5.00 cart discounts $5.00, not more.
Create a coupon
Coupons are operator/console-managed: you create, list, and read them from
the console. A coupon’s value is validated on create — a percent value must
be 1..100 and a fixed value is integer cents off the subtotal (a $5-off
coupon is value: 500) — and a duplicate code is rejected. The storefront never
creates coupons; it only applies a code that already exists.
Preview the totals (storefront)
Before you place an order, preview exactly what the coupon discounts. The preview runs the identical catalog-price, group-pricing, coupon, and fail-closed shipping/tax legs that placement runs — but writes nothing and consumes no redemption. It is a storefront-token route (call it server-side, where the token lives):
POST /v1/tenants/{tenant}/checkout/preview HTTP/1.1
Host: api.ruo.pro
x-storefront-token: <storefront token>
Content-Type: application/json
{ "items": [{ "variant_id": "var_123", "quantity": 1 }], "coupon_code": "WELCOME10" }{
"subtotal_cents": 4999,
"discount_cents": 500,
"shipping_cents": 500,
"tax_cents": 393,
"total_cents": 5392,
"currency": "USD",
"coupon_code": "WELCOME10"
}Apply it at checkout
Pass coupon_code on the checkout body. The placed order records the applied
code and the resulting discount_cents.
const result = await store.checkout.submit(
{ customer_ref, items, card, coupon_code: "WELCOME10" },
{ idempotencyKey: `cart-${cartId}:attempt-1` },
);Refusal codes — preview and placement agree
A refused coupon returns the same stable code whether you previewed or
placed, so the two never disagree. The SDK throws FlightdeckError; branch on
err.body.error.code:
| Code | Status | Meaning |
|---|---|---|
coupon_not_found | 404 | No coupon with that code for this tenant. |
coupon_inactive | 412 | The coupon exists but is not active. |
coupon_exhausted | 412 | times_redeemed has reached max_redemptions. |
Beyond coupons: customer-group pricing
Coupons are per-order codes. For standing, account-level discounts there are
also customer pricing groups — operator/console-managed, with a
default_discount_bps applied to a group’s members automatically at pricing
time. That group discount and a coupon both flow through the same
server-computed discount_cents.
Related
- Integrate checkout — placing the order the coupon applies to.
- Tenant-facing API — the routes reachable with a credential.
- Money invariants — integer cents and server-authoritative totals.