Skip to main content
Every subscription business in the world quietly bleeds. Not from churn you can see — the customer who clicks “cancel” — but from the failure you never notice: the card that declines on renewal day because the customer got paid on the 28th and you tried to charge on the 1st. The bank that was down for an hour. The card that expired. Globally, involuntary churn is 20–40% of all churn. In Nigeria, where most cards are debit cards tied to a salary that lands once a month, it is worse. Most billing platforms will tell you what you earned. Duro is built around a different number: what you almost lost, and got back. That single inversion changes the whole system. When recovery is the product — not a settings toggle buried three menus deep — every decision downstream gets sharper: when you retry, which rail you retry on, what you say to the customer, and how you prove to a judge (or a CFO) that the money came back.

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 a failed_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.
Every one of those branches is configurable per merchant, and every recovered naira is counted on a live dashboard. Read the recovery chapter →

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

1

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.
2

Walk the architecture

Architecture → Overview is the whole system at altitude, then the data model and request lifecycle.
3

Go deep on the engine

The Billing Engine and Recovery chapters are where the real algorithms live. This is the part worth your attention.
4

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.