Orixa Engineering

Offline-First POS Sync: How Checkout Works Without Internet

Local billing, queued stock deductions, and conflict-safe replay — how a checkout counter keeps selling when the internet does not cooperate.

By Samiul Arafah Dhrubo · August 26, 2026

Why offline-first is non-negotiable in Bangladesh. Load-shedding cuts power to routers, fiber lines get dug up by construction, and shops in basement markets or rural bazaars sit in dead zones where mobile data never reaches. A cloud-only POS turns every outage into a closed counter — customers standing at the register with no way to bill. Offline-first inverts the assumption: the local device is the primary system of record during business hours, and the cloud is the replication target, not the dependency.

What runs locally

Three capabilities must survive total internet loss, so they run entirely on the shop floor device:

  • Billing: barcode scan, price lookup from the locally synced product catalog, discounts, VAT calculation, baki (customer credit) recording, and receipt generation — full checkout with zero network calls.
  • Stock deduction queue: each sale writes an inventory event to local storage and appends it to an outgoing sync queue, so on-hand counts stay live on this terminal even while disconnected.
  • Receipt printing: thermal printers connect over Bluetooth/USB directly; receipts render from local data and never wait on a server round-trip.

The product catalog, prices, and customer list sync down to the device whenever connectivity exists — a few MBs that fit comfortably on any POS hardware. The one thing we deliberately do not do offline is card/wallet authorization: bKash merchant payments need the network, which is why the payment flow has its own path (below).

The queue-and-replay sync pattern

 [Checkout on POS]  Sale completed offline
        │  written to local DB + outbox queue (ordered by
        ▼  monotonically increasing local sequence number)
 ┌──────────────┐    reconnect detected   ┌──────────────────┐
 │ Outbox queue │ ──────────────────────▶ │ Server replay    │
 │ sale events, │  batched, oldest first  │ validates stock, │
 │ idempotency  │ ◀────────────────────── │ applies ledger   │
 │ keys         │   ack per batch /       │ events           │
 └──────────────┘   conflict report       └──────────────────┘
        │                                          │
        ▼                                          ▼
 Receipt already                          Conflict resolution:
 printed from                             LWW per product + audit log
 local data

Every queued event carries an idempotency key (device ID + local sequence), so a retry after a dropped connection can never double-apply a sale. The server replays events in sequence order, revalidates them against current server state — sufficient stock, valid product, sane totals — and acknowledges each batch. Only after an explicit server ack does the client mark the event as synced and remove it from the outbox.

An offline-first system must treat reconnecting as a routine operation, not an emergency — if two days of sales cannot replay cleanly in seconds, the sync design has already failed.

Conflict resolution rules

Conflicts are inevitable when two terminals sell the same SKU while offline. Our rules are deliberately boring and auditable:

  • Sales always win: money that changed hands is never reversed automatically; if stock goes negative after replay, we accept the negative balance and flag it rather than un-sell reality.
  • Last-write-wins per product for master data only — price edits or product renames — keyed on server timestamp.
  • Every conflict writes to an audit log with both versions, timestamps, device IDs, and the rule applied, so owners review discrepancies instead of losing them silently.
  • Transfers replay atomically as matched pairs (out + in); a half-arrived transfer parks in a pending state visible on both branches' dashboards until resolved.

What bKash/Nagad reconciliation looks like offline

When the internet drops, cash and baki keep flowing but mobile wallet payments become a deferred problem. The POS records these sales with payment status wallet_pending: the cashier takes the customer's bKash/Nagad confirmation SMS as proof, and the sale completes normally with a printed receipt noting the pending verification.

On reconnect, the sync engine pushes the pending wallet transactions into a dedicated reconciliation view rather than marking them paid. The owner matches each entry against the merchant statement — transaction ID, amount, timestamp — inside a tolerance window (amount exact, time within ±15 minutes). Matched transactions flip to settled; unmatched ones surface as exceptions with both sides shown. This turns what used to be an end-of-day notebook exercise into a queue with a clear count: “7 pending, 6 matched, 1 needs attention.”

Testing approach: the flight-mode suite

Offline behavior that is not tested continuously will rot quietly, so we test it like a feature, not an accident. Our end-to-end suite runs the real checkout flow with the network layer in flight mode: complete a sale, kill the connection mid-batch, restart the app, corrupt a queue entry on purpose, then restore connectivity and assert the replay converges to exactly the expected ledger state. Two assertions matter most — no sale is ever lost or duplicated across any crash/reconnect sequence, and every conflict produced a corresponding audit-log row. The suite runs in CI on every PR, because the most dangerous regression is the silent kind: everything looks fine until the first rainy-season outage.

Related

See our POS software and our work for electronics retailers and grocery stores.

Need a POS that survives outages?

Book a free consultation. We'll demo offline checkout and show you exactly how sync and reconciliation work for your shop.

Get Free Consultation