The retail system a working boutique runs its counter on
A keyboard-first till, invoicing, inventory, customers and trade reports for Vinny's Fashion Hub. One Next.js codebase, shipped to the web and as an offline-capable Windows desktop app.

Command palette. Every action on the till has a key. Search, customers, new invoice and print are all one shortcut away.
Draft numbering. An offline till numbers its own invoices from a per-device shard, and sync keeps that number.
Thermal printer. Receipts print straight from the till; the same document model renders the PDF.
One discount module. The server, the offline till and this panel call the same function. The result is frozen onto the bill.
Integer paise. Every amount is an integer number of paise from input to PDF. No float touches a rupee.
Dynamic UPI QR. Razorpay generates a QR for the exact amount. The invoice is written once the payment is confirmed.
The problem
The shop ran on a paper book and a phone camera. Invoices were hand-written and unnumbered, stock was whatever the shelf looked like, and a return weeks later had nothing to trace back to. Off-the-shelf POS software assumed a chain, a barcode gun and a monthly fee.
What we built
A single application shaped around the counter's real workflow. The till is the home screen: a draft invoice that pulls garments from the catalogue on a keystroke, applies discounts, settles against cash, UPI or a Razorpay dynamic QR, and prints to the thermal printer. Behind it sit inventory with a stock-movement ledger, customer records by lifetime spend, and reports net of discounts.
Decisions, and why
The desktop app is a window, not a second application
Tauri wraps the live deployment. Bundling the server would have shipped the database service key and the Razorpay secrets to a counter PC. A window keeps every secret on the server and one deployment as the source of truth.
An offline till numbers its own invoices
Sales settled without a network are numbered from the device's own shard, and sync adopts that number. The receipt in the customer's bag is never renumbered, and a retried push finds its invoice instead of creating a second one.
Money is an integer
Every amount is held in paise from input to PDF. No floating-point value ever touches a rupee figure.
Discount rules live in one module
The server, the offline till and the summary panel all call the same function, and the result is frozen onto the bill, so editing a rule later cannot rewrite an old invoice.
Scope
- Keyboard-first till: catalogue, quantity and discount without leaving the keys
- Razorpay dynamic UPI QR, cash and UPI settlement
- Invoices as PDF and as thermal receipts from one document model
- Offline sales with a local SQLite store, outbox and conflict states
- Inventory by variant with a stock-movement ledger
- Invoice delivery over WhatsApp, configurable per shop
- Row-level security and an operator allowlist; no public sign-up
Release history
| Rev | Change |
|---|---|
| 1.5.0 | Offline invoice identity, quantity discount bands, invoice delivery over WhatsApp |
| 1.4.2 | Closed XSS, sync-trust and header gaps before release |
| 1.4.1 | Desktop hands the Razorpay payment page to the browser |
| 1.3.0 | Fractional discounts, keyboard fixes, dynamic UPI QR on desktop |
