Back to all projects

SHEET 01/15Full-stack· 2026

Fundi POS

Multi-tenant, offline-first point of sale for Kenyan shops: an Android till, a Windows/macOS desktop till and a web dashboard on one API, with M-Pesa, multi-store stock and an audit trail.

Fundi POS screenshot

Overview

Fundi runs the till, stock and reporting for small retailers, from hardware stores to pharmacies and boutiques. The Android (Expo) and desktop (Tauri) tills keep a local SQLite database synced against Postgres through self-hosted PowerSync, so selling never stops when the connection does. Owners get sales, margin and stock reports per store and per shift from the web dashboard.

The problem

Small shops in Kenya lose connectivity often, and a till that stops working when the internet drops is worse than a notebook. Owners with more than one branch also have no reliable way to see what they actually keep after cost, per store and per shift.

Constraints

  • Selling must work with no connection for days, on low-cost Android phones and on Windows PCs wired to receipt printers and cash drawers.
  • M-Pesa is the main payment rail, alongside cash, card and store credit.
  • Many tenants on one deployment, each with several stores and staff roles.
  • Cashier fraud is a real risk: voids, refunds and cash counts must be trustworthy.

Key decisions

  1. 01

    A local database on every till

    Both tills keep a SQLite copy of the data they need, synced to Postgres through a self-hosted PowerSync instance. Sync rules are scoped per tenant and store and prioritised, so a new till can start selling once the catalogue and staff have synced, without waiting for the full order history.

  2. 02

    Stock as an append-only ledger

    Stock levels are never stored as a counter. Every sale, adjustment, transfer and delivery is a movement, and current stock is the sum. Offline sales from several tills therefore merge without lost updates, and movements that drive stock negative show up in an exceptions feed instead of being silently overwritten.

  3. 03

    The server decides anything involving money

    A void or refund checks the manager PIN locally for instant feedback, but the server re-verifies it before anything commits. Expected cash at shift close is computed on the server from the order ledger, never accepted from the device.

  4. 04

    One pricing engine for every client

    Pricing, tax and discount rules live in a shared package used by the API, both tills and the dashboard, so a total computed offline on a phone matches the one the server computes later.

  5. 05

    Boring, repeatable operations

    The same Docker Compose files run local development, a full-container smoke test and production. CI deploys on merge, signed Android and desktop builds are released from tags, and a daily Postgres backup goes to Cloudflare R2 with a one-command restore.

Outcome

  • Three clients (Android, desktop, web) running against one API and one data model.
  • A till keeps selling, printing receipts and switching cashiers with no connection, and reconciles automatically when it comes back.
  • Live at app.fundipos.co.ke.

What I'd do next

  • Certified KRA eTIMS integration (the schema is ready; submission is still a stub).
  • Verify the M-Pesa and Pesapal integrations against live sandboxes.
  • Bring the desktop till to parity with mobile for product variants and offline stock adjustments.

Stack

TypeScriptExpo / React NativeTauriNext.jsPayload CMSPostgreSQLPowerSyncSQLiteM-Pesa DarajaDockerGitHub ActionsNx