Back to blog

How We Test Discount Logic: 400 Edge Cases and Counting

Inside our Shopify discount testing: 400 edge cases covering currencies, rounding, and combination matrices, because a wrong price is worse than no discount.

Discount Prime Team
Discount Prime Team
· 5 min read
How We Test Discount Logic: 400 Edge Cases and Counting

A discount app has exactly one job that cannot be wrong: the price. Everything else is a feature. The price is the promise.

We have crossed 400 edge cases in our discount test suite, and we thought it was worth explaining what those cases are and why a small pricing app spends this much effort on them. The short version: a discount that is 12 cents off is worse than no discount at all, because it turns a moment of savings into a moment of doubt. A customer who spots a total that does not add up does not think "rounding," they think "is this store real."

So here is how we keep the price right.

Why discount math is deceptively hard

Discounting one item by 10% is trivial. The difficulty is that real carts do not contain one item and one discount. They contain several products, sometimes several discounts, in one of many currencies, each of which rounds to a different number of decimal places. Every one of those variables multiplies the others.

A single volume tier that can also combine with a free shipping discount, evaluated across three currencies, already has dozens of distinct paths through the math. Each path has to land on the correct cent, and it has to land there the same way every time. That is the surface we test.

The three families of edge cases

Most of the 400 fall into three groups.

Currency and rounding. Rounding order changes the answer. Apply a percentage and round each line, and you can get a different cart total than if you round once at the end. Add multi-currency conversion, which introduces its own rounding step, and the two can disagree by a cent or two. A cent or two is enough to make line items not sum to the total shown, which is the single most alarming thing a checkout can do. We test zero-decimal currencies, high-denomination currencies, and the conversion boundaries where floating point likes to misbehave.

Combination matrices. This is the biggest family. We take every discount type in the app, volume, Buy X Get Y-style rewards, shipping, and order-level, and pair it against every combination rule: combine, exclude, prioritize. Then we check that when two discounts touch, the engine resolves them deterministically. The same cart must never produce two different totals depending on the order the discounts were evaluated in.

Line versus cart interactions. A discount can apply to a line item or to the whole cart, and the two interact. A cart-level percentage sitting on top of a line-level volume price has to compose in the right order, and quantity changes have to reprice cleanly. We test carts that cross a tier boundary mid-edit, mixed-eligibility carts where only some products qualify, and the awkward case where a return drops a cart back below a threshold it previously cleared.

A concrete example

Take a cart with two units at $19.99 and a "10% off order" discount, priced in a currency that rounds to two decimals.

Round per line: each unit becomes $17.991, rounds to $17.99, cart total $35.98. Round at the end: $39.98 less 10% is $35.982, rounds to $35.98.

Those agree here, which is the point. But nudge the price to $19.95 with a three-unit cart and the two methods can split by a cent. The test suite pins which method the engine uses and asserts the line items always sum to the displayed total, in every currency, so the customer never sees arithmetic that does not close.

How the suite grows

We do not sit down and imagine 400 cases. The suite grows two ways.

New features arrive with their own matrix. When we shipped a new discount type, we did not just test it in isolation, we added its row and column to the combination matrix against everything already there. That is why each feature costs more to test than the last, and it is the right kind of expensive.

The other source is production. Every bug that reaches a real store becomes a permanent regression test before the fix ships. The failing cart gets written into the suite so it fails the build if the behavior ever comes back. This is why the count only goes up. We are not chasing a number, we are refusing to ship the same mistake twice. Our second BFCM postmortem has the story of a few of the bugs that earned their place in that suite.

Why this connects to margin, not just correctness

Testing is usually framed as a reliability story, but for a discount app it is also a margin story. A combination that resolves the wrong way does not just show a strange total, it can discount deeper than you intended and quietly erode the margin on every affected order. Correct combination logic is what makes your volume discounts behave the way you configured them, and it is what makes the numbers in your profit analytics trustworthy enough to act on.

It is also why we built dry-run simulation: the same determinism that lets us test 400 cases in a build lets you replay a proposed discount against your real orders and trust the result. If the engine were not predictable, the simulation would be a guess.

Four hundred is not a finish line. It is where we are this month, and it will be a larger number next month, because every store that trusts us with its checkout is trusting the one thing that cannot be wrong. We would rather over-test the price than ever have to apologize for it.

shopifydiscount-testingbuild-in-publicengineeringreliability
Discount Prime Team

About the author

Written by the Discount Prime Team - the people building and supporting Discount Prime, the smart discount and pricing app for Shopify. We share what we learn from helping merchants run volume discounts, tiered pricing, and high-converting promotions every day.

Frequently asked questions

Why is testing discount logic hard on Shopify?

Discounts combine, round, and convert across currencies, and each of those multiplies the number of possible outcomes. A single volume tier that also stacks with a shipping discount in three currencies has dozens of paths through the math, and each path must land on the correct cent. Small errors compound across a cart.

How can rounding cause a discount bug?

Rounding order matters. Applying a percentage then rounding each line can produce a different cart total than rounding once at the end, and multi-currency conversion adds another rounding step. If those steps disagree, the customer sees a total that does not match the line items, which reads as a broken store.

What is a discount combination matrix?

A combination matrix is every pairing of discount types (volume, Buy X Get Y, shipping, order-level) tested against every combination rule (combine, exclude, prioritize). It verifies that when two discounts touch, the engine resolves them the same deterministic way every time rather than producing an order-dependent result.

How does Discount Prime prevent the same bug twice?

Every production bug becomes a permanent regression test. Before a fix ships, the failing case is written into the test suite so it will fail the build if the behavior ever returns. The suite grows with each incident, which is why the edge-case count keeps climbing rather than resetting.

Run profit-first promotions on Shopify

Discount Prime brings eight discount types, margin analytics, and conflict detection into one Shopify-native app.

See pricing

Or see it live in our demo store and watch every feature working on a real Shopify store.

Related articles