Recovery-first
A failed charge isn’t an error log line. It’s a state machine with a payday-aware retry schedule, automatic rail-switching, a dunning email sequence, and a live recovery ledger.
Identity on the phone
A customer saves a card once — verified over WhatsApp — and it works at every Duro merchant. The wallet lives with the person, not the store.
Built for Nigerian rails
Card, bank transfer, USSD, virtual accounts, direct-debit mandates. When the card fails, Duro doesn’t give up — it relays down the rails your customer actually has.
Multi-tenant from line one
Two physically separate Postgres schemas for
test and live, a third for tenant identity. No “mode” column you can forget in a WHERE clause. Isolation by construction.The two ideas everything hangs on
If you read nothing else, read this. The whole codebase is two convictions made concrete.1. Recovery is a first-class subsystem, not a cron job
When a renewal charge fails, most platforms drop a row in afailed_payments table and email you a weekly digest. Duro runs the failure through a decision engine (DunningStrategy) that classifies why it failed, then chooses an action:
- Insufficient funds + it’s not payday yet → wait for the next payday (the 28th, or the 1st–3rd) and retry then. Retrying a broke customer on the wrong day just burns attempts.
- Hard decline (stolen, do-not-honour) → stop hammering the card. Switch rails — USSD, then transfer, then virtual account, then direct debit.
- Expired / unsupported card → don’t retry at all. Pause and ask the customer to update their card.
- Processor error / timeout → transient. Retry on an exponential backoff:
0h, 24h, 72h, 120h, 168h.
2. The payment method belongs to the customer, across merchants
A customer pays Merchant A through Duro’s inline popup, ticks “save to my phone,” and confirms a one-time code over WhatsApp. Duro mints a universal identity keyed to that phone number. When the same human checks out at Merchant B — a completely different business — Duro recognises the phone, and their saved card is one tap away. This is the network effect a single-merchant tokenizer can’t have: a conventional saved card belongs to the merchant. Duro’s belongs to the person. One customer portal shows all their subscriptions across all Duro businesses. Read the identity chapter →The system at a glance
Three Node services, a fleet of shared@duro/* packages, two data schemas per mode, and a worker that turns the crank on time. Everything below this page drills into one box.
How to read these docs
Get the thesis
The Thesis is the five-minute version for someone deciding whether Duro is interesting. Recovery-first, identity-on-phone, why Nigeria changes the math.
Walk the architecture
Architecture → Overview is the whole system at altitude, then the data model and request lifecycle.
Go deep on the engine
The Billing Engine and Recovery chapters are where the real algorithms live. This is the part worth your attention.
Build against it
The API Reference has an interactive playground for every public endpoint. Authenticate with a test key and fire real requests from the page.
This is a hackathon build. The charge gateway is a deterministic simulator today; the live payment-rail integration lands in the July window. Everything else — the recovery engine, identity, webhooks, the multi-tenant data plane — is real, tested, and running against a live Postgres.