← All work
Fintech · Bill payments · 2026
PayZeph
- Client
- Zephra Studio (in-house product)
- Role
- Lead engineer, full stack
- Year
- 2026

Overview
A Nigerian bill-payment app. Users fund a wallet by card, then buy airtime, data, electricity and cable TV through real provider APIs. Built as a Turborepo monorepo with a NestJS API and a Next.js web app.
The challenge
Bill payment is a two-system problem. Money leaves our wallet, then a third-party provider has to deliver the airtime or electricity token, and either side can fail. The core requirement: a user must never lose money to a purchase that didn't go through.
What I built
- Wallet funding via Flutterwave. A pending transaction is written first with a unique reference and verified server-side before the wallet is credited; re-verifying a settled reference is a no-op, so double callbacks can't double-credit.
- Bill purchases through VTpass, with meter and smartcard verification before payment and an automatic wallet refund when the provider call fails.
- Integer balances in Postgres (bigint, not floats) and a unique reference on every transaction, modelled with Drizzle ORM.
- Email OTP verification and refresh-token revocation backed by Redis keys with TTLs.
- A simulation mode: without provider keys, the API falls back to a mock checkout and simulated verification, so the full flow runs locally and in demos.
- Monorepo with shared UI, ESLint, Jest and TypeScript configs; each app has its own Dockerfile, with docker-compose for Postgres and Redis.
Stack
- Next.js
- NestJS
- TypeScript
- PostgreSQL
- Drizzle ORM
- Redis
- Turborepo
- Docker
Results
- Wallet + four bill types on Flutterwave and VTpass
- Shipped in 14 days
What I'd change next
- The wallet debit checks the balance and then updates it in two separate statements, so two concurrent purchases could overdraw a wallet. The fix is a single conditional UPDATE … WHERE balance >= amount (or a row lock), with the debit and the ledger insert in one transaction.
- Purchases that time out, rather than fail cleanly, need a reconciliation job that re-queries the provider's status before refunding.
- Test coverage is still the scaffold's; the debit/refund paths are the first things that deserve real tests.