Benefits
Five coupon types, scope, and caps — one arithmetic everywhere.
Benefit is a discriminated union. Same module prices the cart and stores the snapshot — a discount cannot mean one thing at the till and another in the DB.
| Type | Shape | Example |
|---|---|---|
percent | { type, percent, productDs? } | 10% off |
fixed | { type, amount, currency, productDs? } | ARS 500 off |
multibuy | { type, buyQty, payQty, productDs? } | 2-for-1 |
buyXgetY | { type, buyProductD, giftProductD } | Buy A, get B free |
freeItems | { type, items: [{ d, qty }] } | 2 free coffees |
All five accept optional cap: { amount, currency }.
currency is ARS | USD | SAT. In sats, amounts must be whole numbers.
Scope
Absent productDs means all products. Empty list normalizes to absent.
percent— over those lines onlyfixed— capped at those lines' value in the coupon currencymultibuy— counted per line, not shared across products
Cap
"20% off, up to ARS 5,000." Clamps its own currency; leaves others untouched. Absent/null = no ceiling. 0 is rejected.
Freeze at mint
Editing a definition does not change already-minted vouchers. The benefit is snapshotted on the mint row and signed into the voucher.