How it works

When two deals land on one ticket, you decide what happens

Every POS in the category will tell you it does promotions. Not one of them will tell you what happens when two of them land on the same ticket. That is the question your staff get asked at the counter, so here is the rule, in full.

Deals do not combine unless you say so.

Every deal carries a flag saying whether it may be combined with another, and that flag starts switched off — in the constructor, in the parser, and in the mapping from your own promotion rules. A deal is exclusive until somebody decides it is not. The direction matters: the failure that costs money is the one where two discounts combine by accident.

Illustrative deals and figures, chosen so the sums are easy to follow. They are not a real shop's promotion schedule.

Order subtotal $28.00

20% off your order

Percentage

  • 20% of $28.00$5.60

Applies — it saves more

$4.00 off orders over $25

Fixed amount

  • a flat $4.00$4.00

Excluded

Both deals qualify. Both are exclusive. The customer pays $22.40.

The qualifying deals are sorted by what they save, largest first, and the first one is applied. That is a decision in the customer's favour, not the shop's: when two offers collide the bigger discount is the one that lands. If two deals save exactly the same amount, the one you ranked higher wins — and only then.

When stacking is opened up, the percentages do not compound.

Each deal is scored against the original subtotal and the savings are added together. Nothing is applied to a running balance, so the second percentage is not quietly worth less than the first. This is the difference between a promotion that means what it says and one that erodes as it is applied.

Order subtotal $10.00

20% off

  • 20% of $10.00$2.00

10% off

  • 10% of $10.00$1.00

Savings $3.00 · subtotal after discounts $7.00.

Compounded — the other way to build this — the second deal would be 10% of the already-discounted $8.00 rather than of $10.00: $2.80 off in total, and the customer would pay $7.20. Twenty cents, on every stacked ticket you ever write. It is worth knowing which one you bought.

Three settings, and the safe one is the default.

Which of those two worlds a shop is in is one setting, deals.stacking_policy, resolved per shop. It takes precedence over the individual deals: a deal marked stackable still does not stack while the policy says one deal per order.

One deal per order

Default

The highest-saving qualifying deal applies. Every other one is excluded, even if it is marked as stackable.

This is where an unset, blank or misspelled policy lands. Stacking opens up only when a shop says so in words the system recognises — never by accident.

Cross-category only

One drink deal and one food deal may both apply. Two drink deals never both apply, and neither do two food deals.

Written for bars, where a happy-hour price on a drink and a promotion on food are different offers, but two happy-hour prices on the same drink are not.

Respect each deal’s own flag

Every qualifying deal applies, subject to each deal’s stackable flag, a ceiling on how many deals one order may carry, and a check that two deals are not both claiming the same item.

The permissive setting. A deal that is not marked stackable still cannot double up on an item another deal has already claimed.

What stops a bad setting reaching a customer.

A discount engine is mostly its edge cases. These are the ones that decide whether a promotion costs what you budgeted for it.

A cap is a ceiling, never a floor

A deal may carry a maximum discount. Twenty percent of a large catering order is clipped to that maximum. A cap set above what the deal actually computes changes nothing — it does not top a small discount up.

Under the minimum, it does not discount — and it says how much is missing

A deal with an order minimum is refused on a cart below it, and the shopper is told the exact shortfall to the cent rather than being shown a greyed-out offer with no explanation.

The bill cannot go below zero

Total savings are clamped to the order total before anything is subtracted. Two large deals on a small cart take the cart to nothing, not into credit.

A window is minute-precise, and it can cross midnight

Day-of-week and time-of-day windows are half-open at minute precision: a window that ends at 18:00 has stopped applying at 18:00, not at 18:01. A window whose start is later than its end runs past midnight, so late-night pricing does not silently switch off at 00:00.

A deal scoped to one shop fails closed

A deal restricted to particular locations applies only when the active location is known and listed. An unresolved location gets no scoped deal — a franchise special cannot leak into a session that has not worked out where it is.

Priority only breaks a tie

Priority is the second sort key, not the first. It decides between two deals that save exactly the same amount. It cannot be used to push a cheaper deal ahead of a better one.

What the customer ends up paying.

The register and the kiosk each send one discount to the order, and then read the total the server restates — the till does not get to decide what came off. A number a terminal worked out for itself is a number nobody else can reconcile at close, so the figure on the receipt is the one the order actually carries.

The server has the last word

The discount is submitted to the order and the order comes back restated. The register displays that, not its own estimate.

Deals come from your promotion rules

Active deals are read from your own discount rules — the same records your reporting is built on. There is no second, private list of promotions living inside the app.

No model decides the money

A language model does not choose the discount or produce the amount. That was tried, and it was removed: generated text must never set the money taken off a real ticket.