API (api-v2)

Discounts

Catalogue discounts (automatic or by code) are resolved by a pure function at pricing time. Manual till discounts are a separate path, gated by a staff PIN.


Model

Tables:

  • discount
  • discount_scope: role qualifying or reward
  • discount_bxgy
  • discount_localization
  • order_discount_application, order_line_discount: snapshots on the order
DimensionValues
Triggerautomatic, code
Classproduct, order, buy_x_get_y
Valuepercentage, fixed amount (BXGY reward: free, percentage, or fixed amount)
Scopecategory, section, product, variant. Empty means the whole catalogue.
Conditionsminimum subtotal, minimum quantity
Limitsusage limit, uses per order
Windowstarts at, ends at. A paused flag overrides it.

Routes

RouteGuard
get-discounts, get-discount, get-discount-localizationperm discounts.view
create-discount, update-discount, delete-discountwide discounts.write

The dashboard derives status client-side: paused beats live, and expired beats paused.

Resolution

discount.resolve.ts is a pure function with no database access, so it's unit-tested directly.

  • Product class: the best single discount wins per line. There's no stacking.
  • Order class: split proportionally across lines.
  • BXGY: competes with product discounts rather than stacking (docs/adr/0001).
  • Line discounts apply before tax; the order-level discount applies after. See Pricing rules.

Not supported: free shipping, customer eligibility, one use per customer, combination rules, and loyalty (only a seam exists, in spec §9).

Manual till discounts

A manual discount (percentage or amount, plus a reason of up to 255 characters) needs a staff discount PIN:

RouteGuard
/merchant/staff/v1/get-staff-discount-pins, issue-staff-discount-pin, revoke-staff-discount-pinowner
/pos/v1/verify-discount-pinpos.operate

How the PIN check works:

  • PINs are stored as HMACs derived from JWT_SECRET, so rotating that secret invalidates every PIN.
  • Failed attempts are counted in discount_pin_failure. After too many, verification returns 429 with Retry-After.
  • verify-discount-pin returns the approver's name for the till to display.

At sale time:

  • The PIN is sent again with create-order and re-verified.
  • The approver's name is snapshotted onto the order.

On the till:

  • The manual discount comes off last and is capped at the basket total.
  • Re-applying an unchanged, already-approved discount doesn't prompt again.
Previous
Inventory & locations