Skip to content
All blog
Shopify API Reconciliation Margin

Shopify discounts, taxes and line-level allocation

Chong 7 min read

Shopify tells you what an order was worth. Getting from there to what each product earned, after discounts, shipping and tax, is a harder problem than it appears, and it is the problem that decides whether your margin reporting means anything.

The difficulty is not the API. It is that a discount can be applied at a level where it does not naturally belong to any one product.

Two kinds of discount, and why it matters

A line-level discount applies to a specific item. Ten per cent off this product. The attribution is obvious: that line earned less, and its margin fell.

An order-level discount applies to the basket. A voucher for a fixed amount off the whole order, or free shipping above a threshold. It belongs to no single line, and yet your margin report needs it to.

So the discount has to be allocated across the lines, and the API does provide the allocation Shopify itself used, broken out per line. Read it rather than recalculating it, because your arithmetic and Shopify's will diverge on rounding, and a store doing volume accumulates those differences into a real number.

The common shortcut is to spread an order discount evenly, or proportionally to line value, and either way to compute it yourself. Both produce totals that are close and detail that is wrong, and the detail is the reason you wanted the report.

Where the allocation actually decides something

Two decisions depend on getting this right, and both are decisions merchants make regularly.

Which products to promote. If a basket-level voucher is allocated evenly across lines rather than proportionally, a cheap add-on item absorbs the same discount as the main product and appears unprofitable. Promote accordingly and you have optimised against an artefact of your own reporting.

Which products to stock more of. Margin by product drives buying. If discount attribution is wrong, the ranking is wrong, and the buying decision follows the ranking — see which sales channel is most profitable.

Neither error is visible from the order total, which reconciles perfectly. The total was never the problem.

Tax, which has its own allocation

The same structure applies and it is worth handling separately rather than assuming it follows the discount.

Tax is itemised in the order data, at line level, and after discount rather than before. So an order-level discount changes the tax on each line, and the amount by which it does depends on how the discount was allocated.

Recalculating tax from the line prices you hold, rather than reading what Shopify recorded, is therefore doubly exposed: your discount allocation may differ, and your rounding certainly will.

Read the recorded tax. It is the amount that was actually charged to the customer and it is the amount that has to be accounted for. Derived tax is a reconciliation problem waiting to happen — see indirect tax coding with AI.

Shipping, which is not revenue in the way products are

Shipping charged to the customer arrives as its own component of the order, separate from the goods, and this separation should be preserved rather than collapsed.

Three reasons it matters.

Shipping revenue and shipping cost are different numbers, and the gap between them is a real margin line. Blending shipping income into product revenue makes it impossible to see whether your delivery pricing covers your courier charges — see why Shopee partners with courier companies for how differently that economics works on a marketplace.

Free shipping is a discount, not an absence of cost. The courier still charges you. If free shipping is recorded as simply no shipping line, the cost appears with no revenue against it and lands wherever your chart of accounts puts it.

Shipping is taxed, and its tax is recorded separately.

A store with free shipping over a threshold is running a promotion whose cost is entirely in courier charges, and whether that promotion pays is answerable only if the two sides are recorded separately.

Refunds, which reverse all of it partially

A partial refund is where every one of these structures gets tested at once.

Refund one item from a three-item order that had a basket-level discount, and the question is how much of that discount is reversed. Shopify records the refund with its own line detail and its own tax breakdown, which is the answer — again, read it rather than deriving it.

The failure mode is a refund recorded at the amount refunded, with no line detail, coded against revenue in total. The order total still reconciles. Product-level revenue is now wrong, the returned item still shows as sold, and stock movements have nothing to attach to — see credit notes, refunds and adjustments.

The rule that covers all of it

Take what Shopify recorded, at the level it recorded it. Do not recompute.

The API gives you line items, per-line discount allocations, per-line tax, shipping as its own component, and refunds with their own line detail. Everything needed for correct product-level revenue is present.

The errors in this area are almost never caused by missing data. They are caused by an integration that reads the total and reconstructs the rest, because reading the total is easier and it balances — see how Shopify reconciliation actually works.

Common questions

How should an order-level discount be split across products?

By reading the per-line allocation Shopify recorded rather than recalculating it. Shopify breaks out how a basket-level discount was distributed across the lines, and computing your own version diverges on rounding and accumulates into a real difference at volume. Spreading it evenly rather than using the recorded allocation also makes low-priced add-on items look unprofitable.

Should tax be recalculated from line prices?

No. Tax is recorded at line level in the order data and it is calculated after discount, so recalculating it depends on reproducing both the discount allocation and Shopify's rounding exactly. The recorded amount is what was actually charged to the customer and therefore what has to be accounted for.

Why keep shipping separate from product revenue?

Because shipping charged to the customer and the courier charge you pay are different numbers, and the gap between them is a margin line worth seeing. Blending shipping income into product revenue makes it impossible to tell whether your delivery pricing covers your delivery costs, and it hides the cost of any free-shipping threshold entirely.

What goes wrong when a partial refund has no line detail?

The order total still reconciles while product-level revenue becomes wrong, the returned item continues to show as sold, and stock movements have no line to attach to. Shopify records refunds with their own line and tax breakdown, so the detail is available — the error comes from recording only the amount refunded against total revenue.


Related: what the Shopify Admin API actually returns · how Shopify reconciliation actually works · credit notes, refunds and adjustments


See what you could build

Start a free trial and describe what your business needs in plain language — SmartB Studio builds the module for you.

Start free trial
Get started

No credit card · Cancel anytime · Your data stays yours