How Promotion v2 discounts are calculated
On this page
Use this topic when a cart is charged an amount you did not expect, or when you are designing an offer that will run alongside others and need to predict the total. It covers the arithmetic the engine applies after it has decided which promotions qualify. For how that decision is made, see About Promotions v2.
Independent or cumulative
The store variable promotions.calculation_strategy decides what a discount_on_subtotal reward is measured against. See Store variables.
| Value | A subtotal reward is sized on |
|---|---|
independent |
The subtotal of the lines it can discount. This is the default |
cumulative |
That subtotal less the recorded discounts of every promotion without a subtotal reward, and less those subtotal promotions ranked ahead of it by Priority. A shipping promotion’s recorded discount is subtracted too, and so is any discount this promotion’s own product rewards have already taken |
The lines a subtotal reward can discount are the product lines, less any that the reward’s own exclude scopes name and any product set to Exclude From Price Reductions with the Promotions value. A register sizes the reward the same way.
On a cart holding one $100 product, carrying both a $30 off products promotion and a 50% off subtotal promotion:
independent: $30 plus $50, a $80 discount.cumulative: $30, then 50% of the remaining $70, a $65 discount.
Cumulative is the choice when you want a stated percentage to mean “off what is left”. Independent is the choice when each offer should stand on its own, which is easier to explain to a customer but can discount a cart more deeply than intended.
How a subtotal discount is spread across lines
A discount_on_subtotal reward is not recorded against the cart. It is divided across the product lines in proportion to what each line still has left after the other promotions already on it.
On a $100 line and a $50 line, $30 off the subtotal splits into $20 and $10.
If a product promotion has already taken $40 off the $100 line, the two lines have $60 and $50 left, so the same $30 splits into $16.36 and $13.64.
Shares are worked to the cent so that they add back to the reward exactly. Each share is rounded down, then any leftover cents are handed out one at a time to the lines with the largest remainders, and on a tie to the line added first.
Fixed price for N products
A fixed_price_for_n_products reward takes items in groups of Quantity, in cart order. When Max Eligible is set, Apply first to sorts the items most or least expensive first before they are grouped. Each group is charged Fixed Amount, and the saving is spread across that group’s items in proportion to their prices. A group that already costs less than the fixed price saves nothing.
With Frequency set to once, only the first group is repriced. With no_limit, one group is repriced for each time the condition was met, up to the number of complete groups.
:::warning On the web a group is counted in cart lines, not units. A single line holding three of the same product counts as one item toward a “3 for $50” reward, not three. Offers of this shape work as intended only where the customer adds distinct products. :::
A “3 for $50” reward with Frequency no_limit and a condition of 3 items, on six separate items priced $30, $25, $20, $15, $10, and $5, is met twice and so forms two groups. The first group is worth $75 and saves $25. The second is worth $30, already below $50, and saves nothing. The cart is discounted by $25.
Shipping discounts
A discounted_shipping reward is sized on the delivery line’s price and then capped at it, so a discount can never exceed the charge. On a tax-exclusive store the reward is sized on the charge before tax; on a tax-inclusive store the price already includes tax and is used as it stands.
A 50% reward on a $100 delivery charge with 10% tax added takes off $50, leaving $50 plus $5 tax, a $55 delivery charge.
Caps
Two limits apply whichever calculation strategy is set.
- Per line. A products, subtotal, or shipping reward takes no more from a line than that line still has left, counted from its sale price less what promotions have already taken.
- Per promotion. A promotion’s recorded discount cannot exceed the product subtotal plus the delivery charge. That subtotal is measured gross, on the sale price, so it does not shrink as other promotions apply.
:::warning A fixed-price saving is not capped by what the line has left. On an already discounted line, the recorded discount can exceed the line’s own value even though the price the customer pays stops at $0. :::
:::note A line priced by a Block Price takes no Promotions v2 discount, and its value is left out of the subtotal that sizes a subtotal reward and caps a promotion. It still counts toward a promotion’s conditions. :::
Exclude discounted on the web
A reward with Exclude Discounted set skips a line that is on sale (priced below the list price), that carries a share of a discount on the whole sale, or that a staff member’s discount or another promotion has already reduced. At a register, a promotion ranked after this one does not count. Exclude Discounted applies to discount_on_products and fixed_price_for_n_products rewards. A subtotal or shipping reward ignores it, so a sale item still counts toward a subtotal discount. See How Promotions v2 run at the register.
:::warning The check reads the discounts left on the line by the previous calculation, and that record includes promotions ranked after this one. Where two promotions discount the same line and one of them excludes discounted items, that one applies on the first calculation, then drops off on the next refresh and does not come back. Set Exclude Discounted only where a single promotion touches the line. :::
Checking the result
Open a test cart on the storefront and compare the cart totals against the figures above. Each promotion is listed as its own row in the totals, and every line’s price is already net of the promotions recorded against it, so the totals and the line prices should agree without any further subtraction.
Was this article helpful?
Thanks for your feedback! It helps us improve our docs.