The till: scanner-first search, quick-cash buttons and change worked out, with the shift open. Demo data. Dashboard with takings, real profit after returns, payment mix and top products. Demo data. Customer khata: every credit bill, return and payment that moved the balance, with reminders and numbered receipts. Demo data. Vendor panel: RSA-signed licences with machine binding, seat counts, renewals and revocation. Demo data. Vendor revenue across new sales, renewals and manual entries. Demo data.
On this page
The Problem
Small retail shops (grocery stores, pharmacies, general stores) still run on a calculator, a carbon-copy bill book and a khata notebook of who owes what. The cloud POS products that could replace that are built for a different shop: one with reliable internet, a monthly software budget and no objection to its sales data living on someone else's server.
A POS that actually fits these shops has to solve problems that pull against each other:
- The till cannot depend on the internet. Connections drop for hours. A POS that stops billing when the router does loses sales on the busiest day of the week. Everything a shop does, from billing and stock to receipts, reports and even password recovery, has to work fully offline.
- The books have to be exact. Prices, tax, credit balances and stock are summed across thousands of transactions. Floating-point money drifts by fractions of a paisa until the cash-up stops matching, and selling loose goods by weight makes stock arithmetic just as fragile.
- Two tills must never sell the last packet twice. Once a shop adds a second counter, concurrent sales against the same stock become a real race, and an oversold item is a customer turned away at the door.
- The software has to be sellable without being a hostage. An offline app runs on a machine the customer fully controls, so a naive licence check is defeated by copying the folder or winding the clock back. But a check that locks a shop out every time the internet blips is worse than none.
- Credit, returns and shifts are where shops lose money. Customer credit with limits, returns that refund the right share of tax, supplier payables, and a day-end cash count that cannot be rewritten afterwards are not extras. They are the reason a shopkeeper would switch.
What I Built
SalezFlow POS is a licensed, offline-first point of sale sold as a commercial product, plus the vendor control panel that licenses and supports it. I designed and built both apps on my own, from architecture to the Windows installer:
- The POS: Next.js 14, React 18 and TypeScript running inside Electron, with Prisma over a SQLite file on the shop's own PC. Roughly 80,000 lines of TypeScript, 47 screens, 125 API routes and 45 data models, currently at v1.5.
- The Admin panel: Next.js on Vercel with MongoDB, where licences are issued, signed, renewed, revoked and billed, and where support tickets are answered. Roughly 36,000 lines, 43 pages and 57 API routes.
The shop's business data never leaves its computer. Only four things cross the internet: the licence check, the shop's contact profile, support tickets and password-reset emails.
Offline-first desktop architecture
The packaged app starts a Next.js server on a loopback address only that PC can reach and opens a native Electron window onto it. To the cashier it is a desktop app; underneath it is a full-stack web app whose database is a single SQLite file. There is no server to install and no network dependency. Updates download in the background, install only on the user's click so a busy till is never interrupted, and apply schema changes automatically on the next launch.
Licensing a product that runs offline
The panel holds an RSA private key and the POS ships only the public key, so the POS can verify a licence but can never forge one, even with full access to its own machine.
- Activation binds the signed licence to a machine fingerprint and to the shop's own id, so copying the install folder to another PC, or handing an unused key to another shop, produces an invalid licence
- Day to day, the stored signature is re-verified locally on every page load, so the shop keeps trading with no connection
- Whenever the app is online it silently re-validates every 12 hours (every 30 minutes for a blocked shop) and on reconnect, which is what makes revocation and expiry stick even if the clock is wound back
- Warning and grace windows, seat counts, session length, version caps and support deadlines are each set per licence by the vendor and carry their own RSA signature, so a shop owner who edits their own database only breaks the signature and falls back to safe defaults
- Once the grace window is used up, the lock is enforced in the API, not just on screen, while recovery routes stay open so a renewed shop always unlocks itself
Subscriptions, one-time purchases and instalment plans all run on the same machinery: the expiry date inside the signature is the entitlement. A one-time buyer is held on the version they paid for, and the updater never offers them a build they don't own.
Books that cannot drift
Every amount is stored as an integer in the currency's minor unit (Rs 1,250.00 is 125000), and every quantity as an integer in thousandths of a unit, so 1.5 kg of rice is 1500. Conversion happens only at the edges, when displaying or reading user input, with explicit half-up rounding. Each sale line snapshots the price, tax and cost of the goods on that day, so a supplier's price rise never rewrites last month's margin, and currency and timezone are chosen per shop rather than hard-coded.
Stock that can't be oversold
The stock decrement is a conditional update ("reduce by N, but only if at least N remain"). If a double-click or a second till took the last unit first, the condition fails, the whole sale rolls back and the cashier gets a clear message. The same rule holds for the last box of a dated batch and for fractional weights. Every stock change, whether a sale, purchase, return, count or write-off, lands in a single ledger recording what moved, who moved it, when and why. Batches sell soonest-to-expire first, and an expired box can be blocked at the till.
A till built for speed
One auto-focused search box handles names and barcodes. It is scanner-safe: an exact barcode match always wins over the highlighted row, scanning twice increments the quantity, and a code that matches nothing plays a distinct tone so a mis-scan is heard, not missed. Weight-embedded scale barcodes scan straight in. Quick-cash buttons work out change, bills can be held and recalled at any till without being rung up twice, and thermal receipts (58 or 80 mm) print silently.
Credit, suppliers, returns and shifts
- Customer khata: credit sales post to the customer's ledger automatically, a credit limit stops the sale that would cross it, and overpaying or applying a payment twice is refused
- Suppliers and purchase orders: payables per supplier, purchase orders by print, PDF or WhatsApp, and one click to turn a delivered order into stock
- Returns and exchanges: refunds of the right share of tax, straight back into stock and off the khata; supplier returns reduce the payable
- Shifts: open with a float, record cash in and out, count at close, and print a Z-report that is frozen, so nothing that happens later changes what was signed
- Loyalty and promotions: points are a way of paying rather than a discount, so the bill is still taxed in full; promotions and card offers run for a date window, and a discount cap needs a manager's approval at the till
Reports a shop can actually file
Takings, real profit, top products and low stock on one dashboard. Tax by rate that reconciles to the paisa, takings per cashier, stock valued lot by lot at cost, returns by reason and net profit after expenses and write-offs, each exportable to Excel. For pharmacies, a controlled-drug register is generated from the stock ledger, with prescriber and patient captured on the bill.
More than one till, on the shop's own network
A shop can switch a PC to Main PC mode and add counters over its own LAN: a second PC running the app, or simply a tablet or phone opening the Main PC's address in a browser, paired by scanning a QR code. Every device must be approved by the owner before it can even see the login page, approval is re-checked against the database on every request so access can be cut mid-shift, and the number of approved machines is capped by the licence's signed seat count.
Security in depth
- Requests are guarded in three layers: Edge middleware checks only that a session exists, the server layout checks device approval, first-run state and licence, and every API route re-reads the user (and device) from the database to enforce the minimum role, so a demoted user loses access immediately
- Custom roles on top of owner, manager and cashier, with printed documents held to the same permission as the screens they come from
- Passwords and the recovery PIN are scrypt-hashed, and a forgotten password can be reset by email, by PIN with no internet, or with a one-time support code
- On the desktop the SQLite file is encrypted at rest with a per-machine key sealed by the operating system's keystore
- The clock is checked against a trusted server time: login is refused on a badly set clock, and a clock wound into the past is caught even offline
- The build fails if any
.envfile would end up inside the installer
The vendor panel
Generate, renew, revoke and unbind licences; track revenue and instalments; manage app versions and their support deadlines; publish starter catalogues, legal pages, release notes and setup guides; run referrals; and answer support tickets raised from inside the POS, with every action written to an audit log. There is deliberately no sign-up page: administrators exist only through a CLI.
Outcomes
- A shippable commercial product, not a demo. SalezFlow POS installs on Windows, licenses itself against the live panel, updates itself, and supports subscription, one-time and instalment sales from one codebase.
- Trading never depends on the internet. Billing, stock, receipts, reports, backups and even password recovery work fully offline; online checks fail silently and the shop never sees an error it can't act on.
- Correctness proven, not assumed. 1,020 unit tests cover the money, quantity, tax, return, credit and licensing logic, and 470 integration tests run the real server code against a real database. They show that two simultaneous sales of the last item leave exactly one winner and no orphan bill, that receipt numbers never collide, that two credit sales can't both slip past one limit, and that a closed Z-report never moves.
- Licensing that holds without holding shops hostage. Copying the app, editing its database or winding back the clock all fail against RSA signatures and machine binding, while a shop with no connection keeps trading on its last verified licence.
- From one till to a whole counter. A shop can add a second till, a tablet or a phone in an afternoon over its own network, with every device approved by the owner and capped by the licence.
- One app for every kind of shop. Grocery, pharmacy and general stores get weights, batches and expiry, variants, a controlled-drug register and a two-level category tree from the same build, with the screens a shop doesn't need hidden at setup.
- Built and shipped end to end by one developer: roughly 116,000 lines of TypeScript across both apps, from architecture through security audit, installer and release pipeline.