Urgent.News

What's breaking now, across thousands of outlets.

Tech

Building an Offline-First POS That Stays Consistent With an Online Store

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 —…

We recently released a Windows point of sale system for StoreFlux, a headless commerce backend, that functions seamlessly even without an internet connection and remains perfectly synchronized with the online store it is linked to. This article will delve into the technical intricacies behind this achievement, as "offline-first" has been increasingly used as a buzzword without proper understanding of the underlying infrastructure.

Every action taken at the cash register, such as sales, refunds, shift changes, stock adjustments, and product edits, is recorded in a local SQLCipher database simultaneously with the receipt and stock update, along with a row in an outbox table. A background loop then transfers these outbox entries to the server in the correct order.

Each event contains a client-generated ID, and the server processes each ID only once, ensuring that retrying a request after a network failure is safe. If the network is down mid-request and the register cannot confirm whether the server received the event, it simply resends the identical event with the same ID, and the server politely ignores it to prevent duplicate orders.

The register and server agree on financial transactions because they are tested against each other. The till logic is implemented in Rust, utilizing rust_decimal for precise financial calculations, while the server-side is written in C#. These two different type systems independently perform the same computations, including taxes, split tenders, discounts, and change.

If they ever disagree by even a cent, it indicates a critical bug, not a mere rounding error. Consequently, a parity test is performed, replaying identical sales through both engines and comparing the totals, taxes, and stock counts to the nearest cent. This process has already uncovered two real bugs: discrepancies in refund restock calculations and inaccurate offline refunds being dated at upload time rather than the actual sale time, which is crucial when closing a shift and cash register totals are off due to a refund mistakenly attributed to the wrong shift.

Stock levels are determined by combining the server's central stock data with the local deltas recorded by the register, rather than maintaining an independent count. This approach prevents the risk of two cash registers simultaneously selling the same item in different stores. Counter sales are excluded from analytics that track website orders, ensuring that revenue, tax, and average order value calculations accurately reflect real transactions.

Each receipt serves as a unique lookup key, printed as a barcode or QR code depending on configuration. Scanning the receipt back into the application at a later time allows for reprinting or issuing a refund, eliminating the inconvenience of customers needing to find their original purchase receipt.

Security measures for the system are robust, as the cash register requires a one-time code to activate and generates a token that is hashed and bound to the specific machine's fingerprint. This binding prevents the token from being portable, ensuring that even if someone were to copy a configuration file, the attack would be thwarted.

Furthermore, the local database's encryption key is stored in the operating system's credential store, not in a configuration file alongside the database. Additionally, a register that has been offline for longer than a specified grace period locks itself until it can reconnect to the server, preventing stolen laptops from generating unauthorized sales indefinitely without the ability to revoke the session remotely.

For those interested in the implementation, the system is currently live at storeflux.store/#pos. The author is keen to engage with others who have developed similar systems, particularly those who have addressed the challenge of maintaining financial parity between two independent codebases responsible for computing the same monetary calculations. Parity testing has proven to be an effective solution, but the author is open to exploring alternative approaches.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

CloudFront VPC Origin returns 504? Your origin's security group is lying to you

I built a clean private-ingress edge on AWS: CloudFront out front for TLS, WAF, and Shield; a private Network Load Balancer as the origin; no public load balancer anywhere.

  • CloudFront VPC Origin returns 504 Gateway Timeout error
  • Security group of Network Load Balancer limits inbound traffic
  • Allow traffic from CloudFront's public origin-facing IP ranges

How to Scrape Tweets from X (Twitter) Without Paying for Empty or Fake Results

Disclosure: I built the X Twitter Scraper used in this guide and earn from its usage on Apify. If you have ever paid for a batch of scraped tweets and opened the file to find half of it empty…

  • Open X Twitter Scraper Actor in Apify Console to start scraping.
  • Choose sources like search terms, profiles, or tweet links with filters.

Parsing WhatsApp chat exports: what real files taught me

A WhatsApp "Export chat" file looks easy: one line per message, a date, a name, some text. I wrote a parser for it, then ran it on 28 real exports from public open-source repos plus generated files…

  • WhatsApp chat exports contain ambiguous date formats based on phone settings at export time.
  • Export files lack time zone information, requiring user confirmation and indication.
  • Attachments and invisible characters in exports may cause parsing issues.

More from Monday 2 November →