PayLTE
Story
PayLTE is an everyday money app for Ghana. You add money, send it to another PayLTE user, a mobile money wallet or a bank account, pay, and keep receipts. A parent can give a child a supervised wallet, and schools get one place to be paid. It is a client product for Wirebloc LTD, and I designed and built all of it myself: the product design, the architecture, the API, the mobile app, the admin and school web apps, and the infrastructure. It is not live for real money yet, and the MVP is due for acceptance in January 2027.
The problem
Money in Ghana moves across mobile money, banks and cash, and nothing ties it together. A parent who gives a child cash for school has no view of where it went. A school collecting fees in cash reconciles by hand. A fintech product here has to be right about every pesewa and able to explain any movement afterwards, because a wrong posting is a regulatory incident, not a bug.
What I built
PayLTE is a Turborepo monorepo of TypeScript, with four apps, two small services and five shared packages.
- The mobile app is Expo and React Native. It covers sign-up, identity checks with the Ghana Card, wallet, fund, send, withdraw, receipts, notifications and a Family tab where a parent manages their children's wallets.
- The API is NestJS with Drizzle on PostgreSQL. It has 88 database migrations, thirteen background runners for things like payment polling, reconciliation and settlement, and a contract package that is the single source of truth for its types and error codes.
- The admin app is a TanStack SPA for staff: ledger, reconciliation, identity cases, compliance review, fee and limit policy, terminals and platform configuration.
- The school portal is a second web app for organisations, including a till that runs in a browser at the school.
- Fingerprint identification at school terminals is a small matcher service and a local capture bridge, so a child can pay with a finger and a PIN instead of cash or a phone.
Decisions that mattered
An append-only, double-entry ledger
Every money movement is a journal entry with at least two postings whose debits equal credits, and that rule is enforced in the database, not only in application code. Postings are never updated or deleted: a correction is a new entry that points at what it reverses. A balance is the sum of its postings, and a scheduled job checks the cached balance against that sum. This means any figure a user or an auditor sees can be traced to the events behind it.
Money is whole pesewas, never decimals
Every amount is an integer number of pesewas with a currency beside it, from the database column to the API. There is no floating-point type anywhere on the money path, and the build fails if one appears. This matters because rounding drift in a wallet is invisible until it is somebody's money.
Schools, agents and merchants are one thing
I modelled all three as one "organisation" that differs by what it is allowed to do, instead of three parallel systems. Lifting a restriction is a row change, not a migration, and staff see schools and agents as two views over one resource. It kept the product small enough for one builder to hold in their head.
Provider credentials are sealed and need two people
Credentials for payment, identity and messaging providers live in the database, sealed and write-only, so nobody can read one back. One super-admin proposes a change and a different one approves it after a passing connection test. A swap of provider is a change staff make in the admin app, not a redeploy.
No mock providers at runtime
Test doubles exist only inside tests. Local development, staging and production all run the real adapters against real sandboxes or live accounts. I removed the mock routes, the mock environment flags and the mock database rows after seeing staging drift into running code paths that production never would. An environment that cannot reach a fake cannot be misconfigured into one.
The rules are enforced by the build
The repository has fourteen decision records, and most carry a check rather than a promise. One gate runs schema lint, migration checks, secret scanning and a boundary check that keeps provider names out of business code, and it has to pass before anything merges.
Where it stands
The platform runs on a staging environment and is not live for real money. Runtime settings, sealed credentials and the removal of runtime mocks landed in October 2026. The remaining work before launch is the cutover to live provider accounts and acceptance of the MVP in January 2027. The client's product is unreleased, so this page stays at the level of design and architecture.
Stack
TypeScript throughout: NestJS, Drizzle and PostgreSQL for the API, Expo and React Native for the app, React with TanStack for the admin and school apps, and Turborepo with pnpm for the workspace. It deploys as containers to a managed app platform with managed Postgres.