Unexpected costs at checkout drive roughly 48% of cart abandonment, and it’s the single leading reason shoppers bail out.
If you’re running a custom Discount Function, there’s a good chance you’re shipping this exact bug right now. Your function passes every test in the cart preview. Then a customer hits checkout, and the discount shrinks by a few cents, disappears outright, or applies to the wrong line.
Support tickets pile up. Nobody can reproduce it consistently, because it isn’t random. It’s structural.
A Discount Function isn’t called once. It’s re-invoked at multiple checkpoints, against contexts Shopify never promised would match. Fix that assumption, and the bug disappears.
This article focuses on what the mismatch is between cart and checkout, what the reasons behind cart abandonment are, and issues with the discount function, with key tips to overcome them.
Key Action Points
- Stop keying discount logic to cart.deliveryGroups inside a Discount Function
- Round at the same precision the storefront renders, not what’s convenient in code
- Test cart, checkout, and mobile before publishing any function
- Log function invocation IDs so a support ticket maps to an exact run
- Treat multi-currency stores as a separate test pass, not an edge case
What Is The Cart Vs. Checkout Mismatch?
Your Discount Function isn’t a single calculation. It’s a function that gets called more than once, against contexts Shopify never guarantees will match.

At the cart stage, your function reads cart lines, delivery info, and customer data, then computes a number. Checkout re-runs the same code, sometimes against a thinner context, and computes a second number. When the two disagree, your customer sees one price in the cart and another at checkout.
Expert Take:
Most teams treat a cart-checkout mismatch as a rounding annoyance. It isn’t. It’s a signal your function is assuming context Shopify never guaranteed.
Quick Wins:
- Map every place your function reads cart or delivery data, then flag which fields checkout might not populate
- Treat cart preview and checkout as two separate execution environments, not one
- Log the invocation ID with every discount calculation, cart and checkout both
Why Do Most Shopify Stores Ship This Bug Anyway?
It survives QA for a boring reason. Cart preview is fast to iterate against, so it becomes the default test surface, while checkout gets tested once, manually, before launch, if at all.

A function never tested past the cart page hasn’t been tested. It’s been demoed.
Cart Preview Is Where You Iterate, So It’s Where You Stop
Every code change gets re-verified in cart preview because that’s the fastest feedback loop. Checkout requires a real session, a real address, sometimes a real payment method. So the loop that actually matters gets the least repetition.
Quick Wins:
- Build a checkout smoke test into your deploy checklist, not just cart preview
- Assign one team member to own checkout-stage verification on every function change
QA that stops at “it applies” isn’t QA
A test that confirms the discount shows up in the cart isn’t confirming the discount is correct. It’s confirming the function ran. Those are different claims, and only one of them protects revenue.
Quick Wins:
- Add “matches checkout render exactly” as a pass/fail criterion, not “discount appears”
- Run the same test on desktop and mobile checkout, not just desktop cart
Root Cause 1: Delivery Groups Returns Null at Checkout
If your discount logic depends on delivery address, postal code, or country, this is very likely your bug.

Developers have reported for years that cart.deliveryGroups returns empty at checkout, even though the same field holds real data at the cart stage. A GitHub discussion on Shopify’s function-examples repo confirms it’s a consistent, reproducible pattern, not a one-off glitch.
Shopify Hasn’t Documented This Limitation Prominently
Shopify support has internally acknowledged that deliveryGroups is null by design for certain Discount Function API surfaces. That’s not documented anywhere prominently, so most developers find out in production, usually from a support ticket.
Quick Wins:
- Never gate discount eligibility on deliveryGroups inside a Discount Function
- Move zip and country-based logic to a layer with reliable access to that data, before the function runs
- Add a fallback so the discount fails safely, visibly disabled, not silent and wrong
A Failed Discount Should Be Visible, Never Silent
If your function can’t get the data it needs, the safest outcome is a discount that clearly doesn’t apply, not one that applies incorrectly. Silent failure is what generates the “why did my discount disappear” tickets.
Quick Wins:
- Design an explicit fallback state for every discount rule tied to delivery data
- Surface a clear message when a discount can’t be evaluated, rather than defaulting to zero silently
Root Cause 2: Rounding Drift Between Function and Render
Even sound logic can drift from what checkout displays. In one reported case, the function applied $216.00 while the cart rendered $215.99, a one-cent gap on a single line. Multiply that across a multi-line cart, and it’s a visible discrepancy a customer will flag.

Rounding bugs are rarely bad math. They’re rounding at a different point in the pipeline than the storefront renders.
Round Once, At The Point The Customer Sees
Rounding intermediate values and rounding again at render time is how a one-cent gap gets created. The fix isn’t better math. It’s rounding at a single, consistent point in the pipeline.
Quick Wins:
- Round only once, at the point closest to what the customer sees
- Remove intermediate rounding steps from discount calculation logic
- Diff function output against checkout render on every deploy, not just at launch
Multi-Currency Stores Compound The Problem
It gets worse with multiple currencies. FixedAmount discounts getting double currency-converted on subscription renewal orders is a separate, actively reported bug, but it’s the same root cause: function and render pipeline disagreeing on when conversion happens.
Quick Wins:
- Add a dedicated multi-currency test pass, don’t assume single-currency QA covers it
- Test fixedAmount discounts specifically against subscription renewal flows
The Debug Checklist That Actually Catches This
Shopify’s Discount Function API reference documents the cartLinesDiscountsGenerateRun target and the CartLinesDiscountsGenerateRunResult object it returns. Verify that result against the actual checkout render, not the cart preview. Shopify’s official build guide covers testing the full cart-to-checkout flow, and most teams skip half of it.

Run this on every Discount Function change:
1. Capture Cart Output: Screenshot or log discount amount, line targeted, final total at cart stage.
2. Capture Checkout Output: Same order, same session, at checkout before payment submit.
3. Diff the Two: Amount, line, total must match exactly. Any drift, even $0.01, is a fail, not a rounding shrug.
4. Check Delivery Groups Access: If function reads delivery address, postal code, or country, confirm it doesn’t silently return null at checkout. If it can null, function needs a fallback, not a guess.
5. Verify Rounding Point: Confirm function rounds once, at same precision storefront renders. No intermediate rounding steps buried in the calc.
6. Run On Mobile Checkout: Not just desktop. Mobile checkout is separate render path and separate bug surface.
7. Run Multi-currency Pass, Separate from Single-currency QA: Test fixedAmount discounts specifically against subscription renewal orders that’s where double conversion hides.
8. Confirm Invocation ID Logs on Both Calls: Cart invocation and checkout invocation each need a logged ID, so a mismatch ticket maps to an exact run, not a guess.
9. Test with an Empty or Thin Context: Simulate checkout’s leaner data set (missing delivery info, missing customer fields) and confirm function fails-safe discount visibly disabled, not silently wrong.
10. Sign-off Gate: One team member owns checkout-stage verification and signs off before deploy. Cart preview passing isn’t sign-off.
Stop Testing the Cart. Start Testing the Checkout.
If your QA process stops at the cart page, you haven’t verified your discount. You’ve verified a preview of it.
That gap is where the abandonment numbers live. Unexpected costs at checkout are the leading cause of cart abandonment, and consistent cart-to-checkout pricing cuts that specific abandonment by roughly 20%. A discount that’s right in the cart and wrong at checkout produces exactly that moment.
Fixing this isn’t more test cases. It’s testing the invocation Shopify actually charges the customer against, not the one that’s convenient to iterate on.
Not sure whether your Discount Function has this bug right now? Book a free consultation, and we’ll trace your function’s cart and checkout outputs side by side to find where they diverge.
Frequently Asked Questions
Why Does My Shopify Discount Work In The Cart But Not At Checkout?
What Is Cartlinesdiscountsgeneraterun?
Why Is Deliverygroups Empty Inside My Discount Function?
Why Does My Discount Round Differently At Checkout Than In The Cart?
Can A Small Team Catch This Bug Before It Costs Revenue?
Table of Contents




