> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useduro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Thesis

> Why recovery-first billing is a different product, not a feature — and why Nigeria is where it matters most.

Here is the argument in full, because if you don't buy the thesis the rest of the system looks like over-engineering.

## Subscriptions are a leaky bucket and nobody looks at the holes

A subscription business has two kinds of churn:

* **Voluntary churn** — the customer decided to leave. They cancelled. You can fight it with product and price, and everybody does.
* **Involuntary churn** — the customer *wanted* to stay, but the payment failed and the subscription lapsed. Nobody decided anything. The money just didn't move.

Involuntary churn is **20–40% of total churn** for most subscription businesses, and it's the cheapest to fix because the customer isn't even unhappy — they just need to be charged again, at the right time, on a rail that works. Yet on most platforms, recovery is a checkbox: "retry failed cards 4 times over 2 weeks." Fixed schedule. One rail. No idea *why* the charge failed.

That's leaving money on the floor. Duro treats the floor as the product.

## Nigeria makes the leak worse — and the fix more valuable

The standard retry playbook was designed for American credit cards with revolving limits. It does not survive contact with the Nigerian market:

<CardGroup cols={2}>
  <Card title="Debit, not credit" icon="credit-card">
    Most cards are **debit** cards drawing on a current account. If the account is empty, a retry fails — and retrying an empty account three more times that week just burns your attempts. You have to wait for money to arrive.
  </Card>

  <Card title="Salary lands once a month" icon="calendar">
    Salaries cluster around **payday** — the 28th, or the 1st–3rd. A charge that fails on the 15th has a near-100% chance of failing again on the 16th, and a much better chance on the 28th. **When** you retry matters more than how often.
  </Card>

  <Card title="Cards are the weakest rail" icon="arrows-split-up-and-left">
    Card success rates are volatile. But the same customer can almost always pay by **USSD, bank transfer, or a virtual account**. If the card is the problem, stop using the card.
  </Card>

  <Card title="The phone is the identity" icon="whatsapp">
    Everyone has WhatsApp. Phone numbers are the real primary key for a person. That's the natural anchor for a payment identity — far more than an email a customer barely checks.
  </Card>
</CardGroup>

So the recovery engine that would be a *nice optimisation* in San Francisco is a *core differentiator* in Lagos. Duro encodes all four facts directly:

```mermaid theme={null}
flowchart TD
    F["Renewal charge fails"] --> C{"Classify the<br/>failure code"}
    C -->|"insufficient_funds"| P{"Is it payday?<br/>(day ≥ 28 or ≤ 3)"}
    P -->|"no"| W["Wait for next payday,<br/>retry then"]
    P -->|"yes"| R["Retry now on<br/>exponential backoff"]
    C -->|"expired / unsupported"| U["Pause. Ask customer<br/>to update card"]
    C -->|"hard decline<br/>(stolen, do_not_honor)"| S["Switch rail:<br/>USSD → transfer →<br/>virtual account → direct debit"]
    C -->|"processor_error / timeout"| R
    W --> M["Recovery dashboard:<br/>money at risk → recovered"]
    R --> M
    S --> M
    U --> M
```

This is not a flowchart we drew for the docs. It is, almost line for line, the `DunningStrategy.decide()` function. [See the code-level walkthrough →](/billing/dunning)

## The second bet: unbundle the wallet from the merchant

The great lock-in of conventional billing is that the saved card belongs to the merchant. The customer re-enters their card at every new store. That's friction the customer eats and the merchant pays for in abandoned checkouts.

Duro moves the saved payment method to a **customer-owned identity** keyed by phone number and verified over WhatsApp:

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant A as Merchant A checkout
    participant D as Duro
    participant W as WhatsApp
    participant B as Merchant B checkout

    C->>A: Pay + "save to my phone"
    A->>D: Tokenise + link to phone
    D->>W: Send OTP
    W-->>C: 6-digit code
    C->>D: Verify code
    Note over D: Universal identity created,<br/>card linked to the person
    C->>B: Checkout, months later, different store
    B->>D: Known phone?
    D-->>B: Yes — one-tap with saved card
```

The more merchants on Duro, the more valuable each customer's saved identity becomes, and the lower every merchant's checkout friction gets. That's a network effect a single-merchant tokenizer structurally cannot produce. [See the identity chapter →](/identity/universal-identity)

## What this buys the people who matter

<AccordionGroup>
  <Accordion title="For the merchant" icon="store">
    Revenue that would have silently churned comes back. They see "₦X at risk → ₦Y recovered, Z% recovery rate" as the first number on their dashboard, not buried revenue analytics. They configure their own retry rails, payday-awareness, and dunning emails.
  </Accordion>

  <Accordion title="For the customer" icon="user">
    One WhatsApp login shows every subscription across every Duro business. Update a card once, fix every past-due charge. Pay at a new store without re-entering anything.
  </Accordion>

  <Accordion title="For the engineer / judge" icon="code">
    A clean, OOP, fully-typed monorepo where the marquee features are *real*: a state machine, a payday-aware retry scheduler, rail-switching, SSRF-guarded signed webhooks, multi-schema tenant isolation, and an integration suite that runs the whole money loop against Postgres on every push.
  </Accordion>
</AccordionGroup>

Now go look at how it's built. Start with the [architecture overview](/architecture/overview).
