Discounts are server-authoritative and modeled as a fee group
Context
Inkress checkout needed real discount codes. The transaction calculator already expressed shipping, tax, platform and provider charges as fee groups applied in a defined order, and the original intent was for discounts to be "just another fee" — but that had never been built, so discounts did not exist end to end.
Two forces shaped the decision:
- Trust. A discount lowers what the customer pays. If the client could send the discount amount, a tampered request would pay an arbitrary price — the amount must be decided by the server.
- Uniformity. A discount has to behave identically whether the cart is a list of products or a single amount, appear consistently as
discount_totalon the fees quote, the order, and the transaction, and never drive a total below zero.
Decision
A discount code is a merchant-owned definition (percentage or fixed amount, with optional currency, minimum spend, expiry). The client sends only the code; the server resolves it, validates it, and computes the money itself.
The resolved discount enters the transaction calculator as a first-class "discount" fee group, sorted to run before tax, with the merchant as the fee payer so it reduces the total exactly once (surfaced as discount_total) rather than being counted as a customer-paid fee. A fixed discount is clamped so the total cannot go negative; a percentage is bounded to 100% at definition time.
The public quote endpoint, GET|POST /public/m/:username/discount, returns the discounted fee breakdown or a typed rejection reason so checkout can show the buyer a code's worth before they commit; order-create re-resolves the code against the authoritative subtotal and currency and freezes the applied amount and code onto the order.
Consequences
- Positive: one code path prices discounts for every cart shape;
discount_totalis consistent across quote, order and transaction; a client-supplied amount is never trusted. - Positive: downstream money paths (payment adapters, refunds, reconciliation) operate on the discounted authoritative total, inheriting the discount with no special-casing.
- Neutral / to revisit: new discount behaviour (caps, scoping) is added by extending the resolver and the discount fee group — see ADR 0003 and ADR 0005 — not by touching the rest of the calculator.
Alternatives considered
- A separate discount subsystem outside the fee calculator: rejected — it would duplicate the ordering, currency and rounding the fee engine already owns, and risk the discount and the fees disagreeing.
- Trusting a client-sent discount amount (validating only the code shape): rejected — it makes the price forgeable; the amount must be server-computed.