Your total and the POS total differ by a few paise on every ticket
In short
Small differences between the total you showed the guest and the total the POS recorded are usually rounding, and the way to be sure is to reconcile a day of orders line by line rather than staring at one ticket. A constant offset points at a charge or tax rule applied in the wrong order; a difference that grows with line count is rounding. Either way, the POS number counts.
Key takeaways
- The POS computes the number that goes in the till. Your cart produces a quote, and reconciles against it afterwards.
- A difference that scales with line count is rounding. A constant difference per ticket is a rule applied in the wrong order.
- Rounding at the line and rounding at the check produce different totals on the same order — 2 paise on a 3-line example.
- Tax-inclusive item pricing quoted as exclusive produces a large, consistent gap that no rounding argument explains.
- Reconcile daily against the check, alert on drift beyond a stated tolerance, and never reimplement the POS tax engine.
Start from the position that settles most of these arguments: the POS is the pricing authority. It recomputes tax from each item's tax category, applies service charges from its own per-location configuration, rounds by its own rules, and the number it produces is what the restaurant banks. Your cart total is a quote you showed a guest. The engineering question is not how to make the POS agree with you; it is how to tell a harmless difference from a wrong rule.
That distinction is worth real money in both directions. A few paise per ticket is noise. The same few paise, in the same direction, on every ticket carrying a service charge is a rule applied against the wrong base, and at 400 covers a day it is a number the operator will eventually ask about. This is one of the properties of the object your order becomes — the check: what an online order turns into.
Reconcile a day, not a ticket
One ticket tells you a difference exists. A day of tickets tells you which of 5 causes produced it, because the pattern across orders is the diagnosis and the single order is not.
- Export a day of orders with, per order: your quoted subtotal, discounts, service charge, tax and total, and the same 5 figures from the check the POS created.
- Compute the difference at each of the 5 levels, not just at the total. A total that differs by 2 paise while the tax differs by 12 and the discount differs by 10 is 2 bugs cancelling, and it will stop cancelling.
- Add 3 attributes to each row: dining option, line count, and whether the order carried a discount or a service charge.
- Group by those attributes. Rounding shows up as a difference correlated with line count; a rule error shows up as a difference correlated with dining option or with the presence of a charge.
- Plot the sign. Differences scattered either side of zero are rounding. Differences all in one direction are a rule, and the direction tells you who is currently absorbing the money.
Rounding at the line or rounding at the check
The commonest cause and the most benign. Take 3 lines of 99.00 each with a two-component tax of 2.5 per cent each — the arithmetic is the point, not the band, and the applicable band should come from the restaurant's accountant.
| Where rounding happens | Working | Tax total |
|---|---|---|
| Per line, per component | 99.00 at 2.5 per cent is 2.475, rounded to 2.48; two components is 4.96 a line; 3 lines | 14.88 |
| Per check, per component | 297.00 at 2.5 per cent is 7.425, rounded to 7.43; two components | 14.86 |
| Difference | 2 paise, growing with the number of lines and the number of components | 0.02 |
Nothing here is wrong. Both answers are defensible and the POS has picked one. Match its level and its rounding mode where you can discover them, accept the residue where you cannot, and never let the cart's rounding decision be an accident of whichever floating-point path the code took. Work in minor units — integer paise — and round once, deliberately, at the level you chose.
Tax-inclusive item pricing, quoted as though it were exclusive
This one is not rounding and it is not small. If the POS holds menu prices as tax-inclusive and your cart treats the same figure as a pre-tax amount, you add tax to a number that already contains it. The gap is a clean percentage of the subtotal, identical in shape on every order, and it survives every rounding fix anyone tries.
The tell is the ratio: divide the difference by the subtotal and see whether it lands on the tax percentage. The fix is in the sync, not in the cart — carry the inclusive or exclusive flag through from the platform alongside the amount, per item, rather than assuming a convention for the whole menu. That flag is one more field the mapping has to hold, alongside everything else in the mapping table between your ids and the POS.
Service charge before the tax or after it
A service charge is not a negative discount. It may itself be taxable, it may be distributed to staff, and where it sits relative to the tax calculation changes the total. Applied to the pre-tax subtotal and then taxed, it produces one number; applied to the post-tax total, it produces another; and both are configured per location rather than per brand.
Charges also appear conditionally, which is what makes them hard to quote. A charge that only applies above a party size is invisible in every test order of 2 covers and appears on the first table of 20 — the situation worked through in before you accept a group of twenty. Read the charge configuration per location and treat the conditions as data, not as a constant.
The discount applied to the wrong base
A percentage discount has to be applied to something, and the candidates differ: a single line, a category of lines, the subtotal before charges, or the total after them. Apply 10 per cent to the post-charge total in your cart and to the pre-charge subtotal in the POS and the difference is neither small nor stable — it moves with the size of the charge.
- Send the discount as an instruction, not as an amount, wherever the API allows it. Let the POS compute the value from its own base and return the result.
- Where you must send an amount, send the base you used as well, so a mismatch is legible instead of mysterious.
- Expect refusals. Comps, loyalty redemptions and gift cards are frequently not writable through an order API at all, which is its own category of surprise — the things a POS order API quietly refuses.
- Re-quote before you take payment. A guest who was shown a total and charged a different one is a support ticket regardless of who was arithmetically right.
The channel price tier your cart never asked for
Many POS platforms support several price lists — dine-in, delivery, an aggregator-uplift menu — selected by dining option or by order source. A cart that reads the default list and sends a delivery order will be quoting from the wrong tier, and the difference will be large, consistent, and correlated exactly with the dining option column you added in step 3.
Read prices for the tier you are about to sell at, not the tier that happens to be first in the response. This is the same class of per-location, per-channel configuration that makes a menu sync heavier than it looks, described in the full menu sync that never finishes.
Reconcile, and resist reimplementing the tax engine
The tempting response to all of this is to build a pricing engine that reproduces the POS exactly. It is a trap. You would be maintaining a second implementation of rules the restaurant changes on a terminal you do not own, and the moment a manager edits a charge at one location your engine is wrong and confident.
Quote fast, accept the POS's answer, and reconcile every day. A cart that tries to be right is a cart that will be wrong in a way nobody notices for a month.
| Pattern | Most likely cause | Where the fix belongs |
|---|---|---|
| Small, both directions, scales with line count | Rounding level or mode | Your cart — match the level, work in paise |
| Constant percentage of subtotal | Tax-inclusive pricing read as exclusive | The menu sync — carry the flag per item |
| Only on orders carrying a service charge | Charge applied before or after tax | Read the per-location charge configuration |
| Only on discounted orders, moves with charge size | Discount applied to the wrong base | Send the instruction, not the amount |
| Large, correlated with dining option | Wrong channel price tier | The menu sync — select the tier before quoting |
Store both totals and their difference on your order record at acceptance, run the comparison as a daily job, and give an operations user a screen that shows yesterday's drift by cause. That is internal tools and ops work and it pays for itself the first time an owner asks why the figures do not tie. If you support several platforms, decide early whether reconciliation lives per integration or in a shared layer — the trade in one integration per POS, or an abstraction over all of them.
Two neighbouring problems worth naming so they do not get folded into this one. A cloud POS and a terminal on the counter compute and expose totals differently, which is one reason they are two different jobs. And if the day's figures are wrong because synthetic orders reached a live report, that is your test orders in the owner's sales report, not a rounding question. The rest of this silo sits under integrating with the POS on the counter, inside our restaurants and food service practice.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Who should calculate tax, the online cart or the POS?
The POS, always. It holds each item's tax category, the per-location charge configuration and the rules that apply to how the order leaves the building, and its answer is the one recorded against the check. Your cart should quote a total that is close enough to be honest, then reconcile against what the POS computed rather than trying to be authoritative.
Is a difference of a few paise per ticket worth fixing?
Usually not, if it falls on both sides of zero and grows with the number of lines — that is rounding, and both answers are defensible. It is worth fixing when the difference always runs the same way, or appears only on orders with a discount or a service charge, because that is a rule applied to the wrong base and it scales with volume rather than cancelling out.
Why is the gap always the same percentage of the subtotal?
Because the menu prices are almost certainly tax-inclusive and your cart is treating them as exclusive, so tax is being added to a figure that already contains it. Divide the difference by the subtotal: if it lands on the tax percentage, the fix is in the menu sync, where the inclusive flag needs to travel with each item's amount.
Should we rebuild the POS pricing rules in our own system?
No. You would be maintaining a second copy of rules that restaurant managers change on a terminal you do not control, and it will silently diverge the first time someone edits a service charge at one location. Quote from synced prices, treat the POS total as authoritative at acceptance, and run a daily reconciliation with an agreed tolerance instead.
- pos
- reconciliation
- tax
- diagnostics
The work behind this page
Builds from our portfolio that this page draws on.
Read next
- External IDs: the mapping table nobody designs until it breaksThe identity map between your catalogue and the till is the contract that makes every order possible, and 4 routine events break it silently. Design it as a versioned artefact.definition
- The full menu sync hits the limit before it finishesA sync that works on a 40-item menu and dies on a 400-item one is not too big. It is making one request per item, at the same minute as every other location.diagnostic
- It works at the first location and returns an auth error at the other fourIdentical code, one store working and four rejecting, means authorisation was granted per merchant location and only completed once. The fix is an inventory, not a retry.diagnostic
- Order source and dining option: two fields your reporting rests onTwo small POS fields decide whether an injected order is taxed, routed and attributed correctly — and a wrong value is invisible until the first report nobody can answer.definition
- The order lands in the POS and nothing prints at the stationThe POS accepted your order and the line never saw it. Routing is configuration — dining option, revenue centre, station map — and no field in your payload can override it.diagnostic
- The POS says it sent the event and your system never movedA status event that arrives after service is worth nothing. Five places it dies before your handler, and why the durable fix is a polling sweep over open orders rather than better webhook code.diagnostic
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation