Boostorder Knowledge Base
Knowledge Base › Product Director › Coupon Feature — How It Behaves & Where the Edge Cases Are
Product Director

Coupon Feature — How It Behaves & Where the Edge Cases Are

Coupon Feature — How It Behaves & Where the Edge Cases Are

Summary: Behaviour notes, known limitations, and edge cases for product decision-making.

See it yourself: Two Usage-Limit Systems — a side-by-side walkthrough of the real admin screen showing the old flat-cap system versus the newer promotion discount-pool system.

Main content

How it's built

A coupon is its own independent thing — it has its own rules for who can use it and how many times, separate from any specific order. Coupons come from two sources: created by hand by an admin, or created automatically when a trade promotion is approved (this second source only exists on the ci branch right now, not in production).

Who a coupon applies to

This can be controlled by customer account, customer group, product, product group, and — for the automatic promotion coupons — sales region, sales channel, and customer type. Each of these can be set as "only these" or "everyone except these," and those two modes aren't handled the same consistent way across the board. This is worth a closer look from a design standpoint, since the same restriction can currently be built in more than one different way, which invites mistakes.

How usage limits work

There are two separate systems doing this today, and they don't talk to each other:

  • The original system: a simple total-use cap and a per-customer cap.
  • The newer promotion system: a running "how much of this product can still be discounted" pool, shared across every version of the same promotion — so if a promotion gets edited and re-approved, past usage still counts against the new version. The discount amount is always capped so it can never exceed what that item is worth.

A single coupon uses only one of these two systems, never both.

What happens when an order is cancelled

Whether a coupon gets released back to the customer depends on a configurable list of which order statuses count as "cancelled." For the automatic promotion coupons specifically, there's a special rule: if the coupon expires or gets replaced while it's already attached to an order still in progress, that order keeps what it already had, but can't add more, and nothing already given can be taken away.

Who can edit a coupon

Coupons created automatically by a promotion cannot be edited or deleted through the normal admin screens at all — the system blocks it outright. Any change has to happen by fixing and re-approving the promotion itself. This is deliberate, so it's worth double-checking this matches what we actually want long-term, since it means even support staff can't hand-fix a broken promotion coupon directly.

Architecture & API-level reference

This section is written for scoping decisions — so that any future enhancement to coupons (or a similar module) can be designed without breaking what customers already rely on. It covers the create/update payload contract, its constraints, and how the two usage-limit systems are actually enforced under the hood.

The coupon record and its create/update payload

A coupon is one record with a fixed set of fields — there's no separate schema per coupon type. The same payload shape is used whether a coupon is hand-created by an admin or generated automatically by a trade promotion; what differs is which fields get populated and who's allowed to write to it afterward (see "Who can edit a coupon," above).

This is the actual payload shape accepted by the create and update endpoints (both take the same shape):

Field Type Required Constraint
code string Yes Must be unique across all coupons — enforced at the database level. The customer-facing redemption code.
name string Yes Display name.
description string No Free text.
type string Yes One of: cart_discount (whole-order), product_discount (specific products), product_discount_on_msrp (product discount calculated off MSRP, not current selling price).
discount_type string Yes One of: percentage, unit_amount (flat amount). Never both, never a scale that varies by quantity.
discount number Yes The percentage or flat amount. The amount actually applied is capped at checkout so a line can't go negative or free — that cap is computed at apply-time, not stored on the record.
start_at, end_at date No Null on either side means "no boundary" on that side, not "inactive."
max_quantity_limit number Yes Total-redemption cap across all customers. Only enforced for a standard (non-promotion) coupon — see below.
max_redemption_limit number Yes Per-customer redemption cap. Only enforced for a standard coupon.
max_amount_limit number No Accepted and validated, but not currently enforced anywhere in usage/redemption logic — flag this before any roadmap item assumes it works.
min_document_amount number No Same gap as above — accepted, validated, not enforced.
point_needed number Yes Loyalty-points cost to redeem. Independent of the quantity/redemption caps — a separate gate.
is_redeemable boolean No Whether the coupon appears in the points-redemption catalog at all.
is_redemption_required boolean No Whether a customer must explicitly redeem (spend points, create a usage record) before the coupon can be applied to an order, vs. being auto-available at checkout.
is_accounts_excluded boolean No Flips the linked customer-account list from "only these" to "everyone except these." Default (omitted) behaves as "only these."
is_tiers_excluded boolean No Same "only/except" flip, for the linked customer-tier list.
is_products_excluded boolean No Same "only/except" flip, for the linked product list.
is_product_groups_excluded boolean No Same "only/except" flip, for the linked product-group list.
are_bundles_excluded boolean No Not an eligibility scope — a separate yes/no on whether composite/bundle line items can be discounted at all.
image_id number No Must reference an existing media record if present.
tiers_ids, companies_ids, products_ids, product_groups_ids array of IDs No The actual eligibility lists that the four is_*_excluded flags above apply to. Sent as separate arrays alongside the coupon fields, not nested inside them.

Three additional eligibility dimensions — sales region, sales channel, and customer classification — exist only for automatic promotion coupons, as their own ID-list fields alongside the ones above. A customer must pass every populated dimension (it's an AND across dimensions), and tier membership for a promotion coupon is evaluated against the customer's live record at usage time, not frozen at coupon-creation time.

Note: "tier" here means a customer/pricing tier used for eligibility matching — a completely different concept from tiered discount pricing (different % at different quantities), which this system does not support at all (see Known limitations).

What's deliberately absent from this payload: no field for a tiered/scaled discount curve, no per-order multi-attach flag (a coupon-to-order relationship is always one-to-one), and no independent "usage pool" field for standard coupons — the pool mechanism below only exists for promotion-generated coupons and isn't part of this payload at all; it's computed and stored separately once a coupon is linked to a promotion.

Two limit-enforcement mechanisms — how they actually differ technically

The "two systems" aren't two settings on the same coupon — they're two different enforcement code paths, selected automatically by whether the coupon has a linked trade promotion:

  • Standard coupons are checked against the two counters above (total redemptions, per-customer redemptions) by literally counting existing usage records at the moment of use. There's no running balance stored anywhere — it's recomputed every time.
  • Promotion coupons instead carry a stored running balance (a "limit" and a "remaining" amount) that's recalculated from a ledger of quantity-consumption entries, not from counting usage records. Every time a promotion coupon is attached to or removed from an order, the ledger is rewritten for that order and the running balance is recalculated from the full ledger — this is what allows partial/decreasing edits to an order to correctly release quantity back to the pool, while a fully cancelled order releases everything.
  • Cross-version tracking ("lineage"): when a promotion is edited and re-approved, the system generates a new coupon record, but it's linked to the previous version through a shared lineage identifier. The running balance is computed across every coupon in that lineage, not just the current version — so usage never resets on a re-approval, by design, not by accident.
  • Grandfathering rule: if a promotion coupon becomes inactive (expired, or superseded by a newer version) while still attached to an order that's in progress, the enforcement logic explicitly allows that order to keep its already-allocated quantity, blocks any request to increase it, and never re-adds released quantity back to that same order. This is enforced in application logic at the point where an order's coupon usage is synced, not as a database constraint — so any future direct database-level automation of coupon quantities should route through the same sync step rather than writing quantity numbers directly.
  • A coupon can only ever be attached to one order at a time regardless of which system it uses — the "in-progress order" concept above only applies to whichever single order currently holds it.

Who can edit a coupon — enforced at the service layer

The immutability of promotion-generated coupons isn't just a UI restriction — direct create/update/delete calls for a coupon are rejected outright (an access-denied response) if that coupon has a linked promotion, both for single-record and bulk operations. There is currently no override path, including for support/admin roles, other than fixing and re-approving the source promotion. Any future "let support hand-fix one field" request would need a deliberate exception carved into this check, not just a frontend permission change.

Order cancellation — where the behavior is configurable vs fixed

Which order statuses count as "cancelled" for the purpose of releasing a coupon is a single stored setting (one shared list of statuses), not hardcoded per coupon or per coupon type — so this is a legitimate place for product/ops to tune behavior without an engineering change. What is not configurable is the grandfathering rule for promotion coupons described above — that logic runs regardless of what the cancellation-status setting says, because it triggers on the coupon/promotion becoming inactive, not on order cancellation specifically.

Known limitations

  • No support for tiered/scaled discount pricing (different discount levels at different quantities) — a coupon is always one flat percentage or one flat amount. This would require a new payload shape, not just a new value in the existing discount-type field.
  • The max-amount-limit and minimum-document-amount fields exist in the payload and pass validation, but nothing in current usage or redemption logic actually enforces them — worth resolving (either wire them up or remove them) before any roadmap item assumes they work today.
  • A coupon can only be attached to one order at a time.
  • The automatic promotion-coupon feature only exists on the ci branch today, not in production — any roadmap messaging should reflect that it hasn't shipped yet.
  • The "only these" / "everyone except these" exclusion flags are set independently per eligibility dimension rather than through one consistent mechanism, so the same real-world restriction can currently be built more than one way — this is worth a closer look from a design standpoint before building anything that assumes a single canonical representation of "who a coupon applies to."
  • The rules for who a regular coupon applies to have enough different combinations that they'd benefit from a dedicated test checklist, since a small mistake here is hard to spot just by reading the settings.
Was this helpful?