ECM-36Recommended
Apply discounts on the server in one fixed order
Apply promotions in one documented order, such as line discounts, then order discounts, then shipping discounts, then tax. Write which promotions combine as data, and never let a line go below zero.
How to do it
Two coupons applied in a different order give a different total, and the buyer, the invoice and the refund then disagree. Keep each promotion as a record with its type, amount or percentage, start and end, and the promotions it may combine with. Compute the cart on the server only, as Work out every amount on the server requires. Spread each order discount across the lines, as Refund exactly what each returned line cost needs. An announced price cut on goods to EU consumers shows the prior price, as Show the lowest earlier price beside a discount says.
Examples
TypeScript example
const STAGES = ['line', 'order', 'shipping'] as const;
export function applyPromotions(cart: PricedCart, promotions: Promotion[]): PricedCart {
return STAGES.reduce(
(current, stage) => promotions
.filter(p => p.stage === stage)
.sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id))
.reduce((c, p) => p.apply(c), current),
cart,
);
}
Python example
STAGES = ("line", "order", "shipping")
def apply_promotions(cart, promotions):
for stage in STAGES:
for promotion in sorted((p for p in promotions if p.stage == stage), key=lambda p: (p.priority, p.id)):
cart = promotion.apply(cart)
return cart
How to check
A property test applies random sets of promotions to random carts. The total never falls below zero, no line is negative, and the same promotions in any input order give the same total.
Related rules
- SEC-59 Work out every amount on the server in Adversarial testing
- ECM-34 Refund exactly what each returned line cost in E-commerce and fulfilment
- PAY-9 Show the lowest earlier price beside a discount in Payments and billing
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/ecommerce/promotions-and-fraud/discount-order">Agent Wranglers e-commerce and fulfilment playbook</a>. In code you write, add a comment next to it with the same link.