We just shipped a Windows point of sale for StoreFlux (a headless commerce backend) that keeps working with no internet connection and never drifts out of sync with the online store it's attached to. Wanted to write up the actual mechanics, since "offline-first" gets used a lot as a buzzword without the plumbing behind it.
An outbox of idempotent events, not a sync API
Every action at the till — a sale, a refund, a shift open/close, a stock adjustment, a product edit — gets written to a local SQLCipher database in the same transaction as the receipt and the stock change, plus one row in an outbox table. A background loop drains that outbox to the server in order. Each event carries a client-generated id, and the server only ever applies a given id once. That one property is what makes a retry safe: if the network dies mid-request and the register doesn't know whether the server got it, it just resends the same event with the same id, and worst case the server politely no-ops it instead of creating a duplicate order.
The register and the server agree on money, because they're tested against each other
The till logic is written in Rust (rust_decimal for the actual math), the server side in C#. Those are two different type systems making the same calculations — tax, split tenders, discounts, change — and if they ever disagree by a cent, that's a real bug, not a rounding quirk to shrug off. So there's a parity test that replays identical sales through both engines and diffs the totals, tax, and stock to the cent. It's already caught two real bugs this way: refund restock math that didn't match between the two sides, and offline refunds that were getting dated at upload time instead of at the actual time of the sale — which matters a lot once you're closing out a shift and the cash doesn't add up because a refund landed in the wrong shift.
Stock is server stock plus local deltas, not an independent count
A register doesn't keep its own authoritative stock number. It shows the last full stock pull from the server, minus whatever it's sold locally and hasn't uploaded yet. That sounds like a small detail but it's the difference between "two tills at two different shops double-selling the last unit of something" and not — because neither till is pretending to own the real count, they're both just showing a server snapshot plus their own pending deltas.
Counter sales can't be allowed to lie to the analytics
This was a design decision I almost got wrong early on: should a point-of-sale order "count" the same as a website order everywhere? No — specifically not in the conversion funnel. Every order carries a channel now. Counter sales count fully in revenue, tax, and average order value (they're real money), but the website's conversion-rate and cart-abandonment numbers only look at online orders. Otherwise a slow week online looks artificially fine because the shop next door had a good Saturday, and that's actively misleading for anyone trying to diagnose their actual storefront.
Every receipt is also a lookup key
The receipt number prints as a barcode (CODE128, with the module width picked based on paper size so it actually fits a 58mm roll) or a QR code depending on config. Scan that receipt back into the app — at the counter, days later — and it opens that exact sale for a reprint or a refund. It's a small thing but it removes an entire category of "customer lost their receipt, now what" friction.
Security assumptions for something that lives on a shop's PC, unattended
A register activates with a one-time code and gets back a token that's stored hashed and bound to that specific machine's fingerprint, not something that's portable if someone copies a config file. The local database's encryption key lives in the OS credential store, not in a config file next to the database itself. And a register that's been offline longer than a configured grace period locks itself until it can reach the server again — better a cashier has to wait a minute for connectivity than a stolen laptop keeps ringing up sales indefinitely with no way to revoke it remotely.
Live now if you want to see where it landed: storeflux.store/#pos
Curious if anyone else has built something similar — specifically interested in how other people have handled the "two independent codebases computing the same money math" problem. Parity testing felt like the obvious answer once we needed it, but I'd like to hear if there's a cleaner pattern.

