Product

Frollie POS

The POS that runs Frollie's Block M counter on one Android phone.

Status LIVE Since 2026-01
Open source60-second README
What it does
The point-of-sale that runs Frollie, our snacks business in Jakarta. It lives entirely on one Android phone — no terminal, no extra hardware, no monthly SaaS.
Stack
React 19 + TypeScript + Vite, Convex for real-time sync, Tailwind 4 + shadcn/ui, Xendit for payments, the Telegram Bot API for comms. Installable PWA on Vercel.
Run locally
git clone https://github.com/lucasyhzhu-debug/frolliePOS.git cd frolliePOS && npm install Then two terminals: npm run dev npx convex dev
License
MIT — fork it, ship it, make it yours.

What it does

A staffer signs in with a 4-digit PIN and builds a cart from the catalog, then charges by QRIS tap-to-pay or manual bank transfer — digital payments only, money kept as whole-rupiah integers, never floats. The charge step waits for a connection. Carts can be parked mid-sale and resumed, receipts reprinted, and a refund is its own record rather than an edit to the original sale. Stock decrements at the SKU level, and a sale is never hard-blocked at zero; it gets flagged for review.

A manager PIN unlocks the back office: today's totals, payment mix, top SKUs, per-staff numbers, product and staff admin, Xendit settlement tracking, and an append-only audit log. Owners sit on a separate plane at /cockpit — a daily sales rollup, remote approvals, and multiple outlets run from one place.

Frollie POS home screen — New sale, saved carts, history, stock check
Home · New sale
Frollie POS cart — Dubai Chewy Cookies with a running subtotal in rupiah
Cart · QRIS or transfer
Frollie POS payment screen — QRIS code to scan, webhook-confirmed
Payment · QRIS

How it works

Approvals and alerts run on Telegram: off-booth approvals arrive as a single-use link, owners get a daily summary, managers get a live sales ticker and low-stock notices. Payments are webhook-confirmed. QRIS renders in-app via Xendit, and confirmation comes from the payment webhook with a manager-PIN manual fallback. No polling. Auth is PIN-first: 4-digit PINs hashed with argon2id, device registration before first login, lockout on repeated misses. Offline is partial and honest — catalog, cart, drafts, and the stock-in queue work offline; payments, auth, and refunds tell you plainly they need a connection. Every operational query is scoped to the outlet bound to the device.

Stack

React 19 + TypeScript + Vite. Convex for the backend and real-time sync. Tailwind 4 + shadcn/ui for the interface. Zustand holds cart state, IndexedDB holds the offline queue. Xendit for payments, the Telegram Bot API for comms. Hosted on Vercel as an installable PWA.

Contact

Email is the highest‑signal channel.