How to test discount code checkout: the promo rules, not the happy path

A discount code isn't a text box, it's a rules engine — stacking, minimum-order thresholds, expiry timezones, per-customer limits, and the 100%-off edge. Here's how to test discount code checkout end to end in plain English, no scripts.

playbookcheckoutecommerceno-codeqa
monito

How to test discount code checkout: the promo rules, not the happy path

playbookcheckoutecommerceno-code
August 31, 2026

Applying a valid discount code and watching the total drop is the demo. It's also the one path everyone clicks by hand before shipping, which is exactly why it's never where the money leaks. The bugs that cost you real revenue live in the rules around the code: the two codes that were never supposed to stack but do, the "20% off orders over $50" that fires on a $40 cart, the coupon that expired at midnight in a timezone that isn't your customer's, and the 100%-off code that sails past a payment step it should have skipped — or worse, charges shipping on a "free everything" order.

A discount code field looks like a text box. It's actually the front door to a small rules engine: eligibility, thresholds, limits, expiry, stacking, and the interaction between the discount and every other number on the invoice — tax, shipping, the payment total. Testing it means testing the rules, not the reduction. This is the playbook for doing that end to end without writing a line of Playwright, and it deliberately doesn't overlap with testing the payment step itself — that's the card and the charge; this is everything that decides what you charge.

What "testing a discount code" actually has to cover

The valid-code-drops-the-total case is one line. The rules engine underneath it is where the surface is:

  1. Invalid, expired, and typo'd codes. A wrong code should be refused with a clear, specific message — not silently ignored, not applied anyway, and not a generic 500. Test the expired code, the never-existed code, the right code with a trailing space, and the right code in the wrong case. Each should fail honestly.
  2. The minimum-order threshold, on the correct base. "Over $50" raises the question nobody wrote down: fifty dollars before tax, or after? Before shipping, or after? Test a cart one cent under the threshold (refused) and one cent over (accepted), and confirm the number the rule checks against is the number the business actually meant.
  3. Per-customer and global usage limits. A single-use code should work once and be refused on the second attempt by the same account. A "first order only" code should refuse a returning customer. A globally-capped code should stop accepting once the cap is hit. These are the codes that turn into a Twitter thread when they don't enforce.
  4. Stacking. Can a customer apply two codes? Should they be able to? Whatever the intended answer, test it directly — apply one, then a second — and confirm the app does what the business decided, not what the form happens to allow. Unintended stacking is a discount you didn't budget for, multiplied by everyone who finds it.
  5. Expiry, at the boundary and across timezones. A code that ends "August 31" ends when, exactly, and in whose clock? Test it just before and just after its stated cutoff, and be explicit about the timezone, because "expired for me but not for a customer three zones east" is a real and confusing support ticket.
  6. The math, all the way to the payment total. The discount has to survive being combined with tax and shipping and still produce a total that's internally consistent. Percentage-off should compute against the right subtotal; amount-off should never drive a line — or the order — negative. The number the customer approves must be the number they're charged.
  7. The 100%-off and free-shipping edges. A code that zeroes the order is the single nastiest case: does the flow correctly skip or short-circuit the payment step, or does it ask for a card to charge $0 and choke? And does "free shipping" actually zero the shipping line, or just the item subtotal while shipping quietly rides along?

Most teams test item 1's happy sibling — valid code, total drops — ship, and meet items 2 through 7 as revenue leakage or support tickets. "Someone stacked two codes and got the order for free" is not a bug you find by clicking the happy path once; it's a bug you find by trying to break the rules on purpose, every time, which is precisely what an agent does the same way on every run — the reason an agent catches what a script waved through.

Nothing below references a field ID, a selector, or a DOM path. A checkout gets redesigned constantly — the promo field moves, the cart summary gets rebuilt — and a test pinned to today's markup rots on the first redesign. Describing the rule instead of the element is why this approach outlives the checkout it tests.

Prompt 1: the invalid and expired codes, refused honestly

Go to the cart/checkout on https://staging.yourshop.com with at least
one item in the cart. Test how invalid discount codes are handled.

Try each of these in the promo/discount code field, one at a time, and
report exactly what happens for each — the message shown, and whether
the order total changed:
1. A code that does not exist at all (e.g. "NOTAREALCODE").
2. A known-expired code, if you have one; otherwise a plausibly-formatted
   fake (e.g. "SUMMER2019").
3. A valid code with a trailing space and different capitalization
   (e.g. " welcome10 " if the real code is WELCOME10).

For each, confirm the app either clearly ACCEPTS it or clearly REFUSES
it with a specific, readable message. Flag anything that fails silently,
shows a generic error, applies a discount it shouldn't, or throws a
server error. Report all console errors.

Case 3 is the quiet one. Trailing whitespace and casing are the two things a human tester never types but a real customer does constantly, pasting from an email. The intended behavior is usually "trim and normalize, then accept" — but plenty of checkouts refuse the code the customer clearly meant, and that's a conversion bug wearing an input-validation costume. The agent reads the actual rendered message, so "refused with a helpful message" and "refused with a red box that says Error" come back as different outcomes instead of one failed assertion.

Prompt 2: the minimum-order threshold, on both sides of the line

On https://staging.yourshop.com, test the minimum-order threshold for a
discount code that requires a minimum spend (for example "20% off orders
over $50" — substitute the real code and threshold).

1. Build a cart whose subtotal is just UNDER the threshold (e.g. $49.xx).
   Apply the code. Confirm it is REFUSED, and that the message explains
   the minimum clearly.
2. Adjust the cart to just OVER the threshold (e.g. $50.xx). Apply the
   same code. Confirm it is now ACCEPTED and the discount applies.
3. Report exactly which number the threshold is checked against: the
   item subtotal, the subtotal after other discounts, the total after
   tax, or the total after shipping. Quote the on-screen numbers you
   used to determine this.

Flag any case where the code applies below the threshold, refuses above
it, or where it's unclear which base the "minimum" is measured on.

Step 3 is the finding that saves an argument later. "Orders over $50" is ambiguous by construction, and different parts of a codebase often disagree about the answer — the frontend gates on subtotal, the backend re-checks against post-tax total, and a cart sitting exactly on the seam gets accepted by one and rejected by the other. An agent that reports the actual base it observed turns "it feels off" into "the code applies at $50.00 subtotal but the business rule was post-tax, so a $48 cart with tax qualifies when it shouldn't." That's a spec bug you want surfaced in staging, not discovered by an accountant.

Prompt 3: usage limits and unintended stacking

On https://staging.yourshop.com, test usage limits and stacking for
discount codes.

Single-use / per-customer limit:
1. As a logged-in test account, apply a single-use (or first-order-only)
   code and complete checkout to the point it's consumed.
2. Start a new order as the SAME account and apply the SAME code again.
   Confirm it is now REFUSED with a clear message.

Stacking:
3. Add an item, apply one valid code, then attempt to apply a SECOND
   valid code on top of it. Report whether the app allows both to stack,
   replaces the first with the second, or refuses the second.
4. If both stack, report the final total and whether the combined
   discount looks intentional or like it could be exploited (e.g. two
   percentage codes compounding, or a code plus free shipping).

Flag any case where a single-use code works twice for the same customer,
or where two codes stack in a way that looks unintended. Capture the cart
summary and any console errors.

Step 3 is where the money is. Most discount systems are tested for "does the code work," never for "can the customer apply two." The form usually lets you try, and whether the second code stacks, replaces, or bounces is a decision that frequently exists only as an accident of implementation. If the business meant "one code per order" and the form silently allows two, you've shipped a discount you didn't authorize — and the customers who find it will tell each other. Testing the stack directly, every run, is the same discipline that makes an agent-driven multi-step flow catch the leftover-state bugs a linear script never reaches.

Prompt 4: the math to the payment total, and the 100%-off edge

On https://staging.yourshop.com, verify the discount math is consistent
all the way to the amount charged.

1. Apply a percentage-off code to a multi-item cart. Confirm the
   discount is computed against the correct subtotal, and that the
   displayed total = subtotal − discount + tax + shipping, with the
   numbers actually adding up. Quote each line.
2. Confirm tax and shipping behave as intended relative to the discount
   (e.g. tax on the pre- or post-discount amount, per your rules), and
   that "free shipping" codes actually zero the SHIPPING line, not just
   the item subtotal.
3. Apply a code that brings the order to $0 (100% off, or a fixed amount
   ≥ the cart total). Complete the flow. Confirm the payment step is
   correctly skipped or short-circuited — the order should complete
   without asking for or charging a card — and that no line goes
   negative.

Report any total that doesn't reconcile, any negative line, any case
where a $0 order still demands payment, and every console/network error.

Step 3 is the edge that breaks in production because nobody builds a $0 cart by accident during manual testing. A fully-discounted order is a genuinely different code path: the payment step should be skipped, but a checkout that always assumes a charge will happily render a card form for a $0 total and then fail when the payment processor rejects a zero-amount charge — or, the other direction, complete the order but still bill shipping on something advertised as free. The agent reads the final total and the network activity, so "the confirmation screen looked free" and "the charge was actually zero" come back as separately-verified facts instead of one screenshot of a happy page.

Reading the results

Each run comes back as a Monito Session, not a pass/fail bit: a screenshot timeline of the cart at every step, the network log with the real totals and the final order request, console output, and the agent's reasoning wherever it made a judgment call about whether a total reconciled. For discount testing the two documents to read are the cart-summary screenshots — because the bug is usually a number that's individually plausible but doesn't add up with the others — and the final order request, which is the only place the "confirmation said free, invoice said shipping" gap is actually visible.

The prompts survive your redesigns because none of them names a field or a selector — only the rule being tested. Move the promo box, rebuild the cart summary, restyle the whole checkout: the rule didn't change, so the prompt didn't either. That durability is the whole argument for describing intent over recording clicks, and it's why a prompt is a better long-term artifact than a spec for anything with a UI that moves.

The one to run right now

If you run a single check today, run the stacking-and-total test — it catches the two most expensive discount bugs at once (an unintended stack and a total that doesn't reconcile to the charge):

On https://staging.yourshop.com, add two items to the cart. Apply one
valid discount code, then try to apply a SECOND valid code on top of it.
Report whether both stack, one replaces the other, or the second is
refused. Then complete the flow to the payment total and confirm the
final amount charged equals subtotal minus every applied discount, plus
tax and shipping, with the numbers reconciling exactly. Quote each line
of the cart summary and the final order total, and flag any unintended
stack, any total that doesn't add up, or any negative line. Capture
screenshots and all console errors.

Save it as a Test Scenario, pin it to your staging Project, and wire it into CI against every deploy that touches cart, pricing, or promotions — the surfaces where a discount rule breaks quietly and expensively. A full run is typically 8–13 credits — roughly $0.08–$0.13 — and your first run is free. Point it at staging, paste the prompt, and read the invoice. If two codes stack when they shouldn't, you'd rather find out from an agent than from your customers.