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

# Direct Debit Mandates

> A customer authorises Duro to pull renewals straight from their bank account. NIBSS e-mandate, a PENDING → ACTIVE lifecycle a worker watches, and a rail dunning can fall back to.

A direct-debit mandate is standing permission for Duro to charge a customer's **bank account** for renewals, without a card. The customer sets one up from the [portal](/identity/customer-portal); Duro creates a NIBSS e-mandate through Nomba, tracks it from `PENDING` to `ACTIVE`, and once active it becomes another [rail](/billing/dunning) the billing engine can charge on. Mandates hang off the same **global [account](/identity/universal-identity)** as the wallet and cards, so a mandate follows the person, not a single merchant.

<Note>
  Direct debit is a real banking product: it is **live-mode only** and only available when the Nomba client is configured. Everything below is on the `live` schema.
</Note>

## Creating a mandate

From the portal, the customer picks a bank, enters their account number and phone, and Duro does the rest:

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as Customer (portal)
    participant API as /portal/email/direct-debit
    participant N as Nomba (NIBSS)
    participant DB as AccountMandate (core)
    C->>API: POST { bankCode, accountNumber, phone }
    API->>N: resolve account name (bank lookup)
    API->>N: create mandate (VARIABLE, ~5yr, start now)
    N-->>API: mandateId + activation instruction
    API->>DB: store AccountMandate (status: PENDING)
    API-->>C: { id, status: PENDING, instruction }
    API->>C: email - "action needed to activate"
```

* **`GET /portal/email/banks`** returns the banks that support direct debit (a filtered, cached Nomba list, with a built-in fallback). Not every bank is eligible, so this is a **shorter list** than the payout bank list.
* **`POST /portal/email/resolve-account`** takes `{ bankCode, accountNumber }` and returns `{ accountName }` from a live Nomba lookup, so the customer confirms the right account before creating the mandate.
* **`POST /portal/email/direct-debit`** takes `{ bankCode, accountNumber (10 digits), phone }`, resolves the name, then creates the Nomba mandate with `frequency: VARIABLE` (renewals differ in amount), a start \~2 hours out, and a \~5-year window. The mandate row is saved `PENDING`, and the customer is emailed the activation instruction.

Mandate creation retries a transient Nomba error (name-enquiry hiccups, timeouts) up to three times, and maps the common hard failures to plain guidance (an ineligible bank becomes *"This bank does not support direct debit yet. Try another bank or use a card."*).

## Activation is a token payment the customer makes

A NIBSS e-mandate isn't live the moment it's created. The customer has to **consent by making a small token payment** (₦50) from the same account, via their bank app. Until they do, the mandate stays `PENDING`. The exact instruction (account numbers to pay, from the `STANDARD_MANDATE_INSTRUCTION`) is returned on creation and surfaced in the portal for any pending mandate.

## The lifecycle Duro tracks

A mandate lives in one of three states, normalised from Nomba's raw status fields:

```mermaid theme={null}
stateDiagram-v2
    [*] --> PENDING: created (awaiting token payment)
    PENDING --> ACTIVE: lifecycle=ACTIVE and advice=ADVICE_SENT
    PENDING --> SUSPENDED: suspended / deleted at the bank
    ACTIVE --> SUSPENDED: suspended / deleted at the bank
    ACTIVE --> [*]
    SUSPENDED --> [*]
```

* **`PENDING`**: created, waiting on the customer's token payment / bank advice.
* **`ACTIVE`**: usable for charging. Reached only when Nomba reports the mandate lifecycle `ACTIVE` **and** the advice status `ADVICE_SENT`.
* **`SUSPENDED`**: suspended or deleted at the bank; terminal for Duro's purposes.

Two things keep that state fresh:

1. **A worker sweep.** A background job scans `PENDING` mandates in batches, re-queries each one's status at Nomba, and flips any that have gone `ACTIVE`. When one activates, the customer is emailed a "direct debit is active" confirmation.
2. **On-read refresh.** When the customer opens the direct-debit screen (`GET /portal/email/direct-debit`), Duro refreshes any non-suspended mandate's status live before returning the list, and backfills a missing activation instruction for pending ones. So the portal never shows a stale `PENDING` for a mandate that has actually gone live.

The customer can also force a refresh of a single mandate with **`POST /portal/email/direct-debit/{id}/refresh`**, and remove one with **`DELETE /portal/email/direct-debit/{id}`** (ownership is checked against the session's account).

Each mandate view carries `id`, `bankName`, `accountLast4`, `bankCode`, `status`, and (for pending mandates only) the `instruction`.

## Why it matters for recovery

Direct debit is one of the fallback rails in the [dunning](/billing/dunning) chain (`RAIL_FALLBACK = [ussd, transfer, virtual_account, direct_debit]`). A customer whose **card** keeps hard-declining can still be charged on a mandate they've authorised, pulling the renewal straight from the bank account, no card involved. An active mandate is exactly the kind of alternate rail that turns a lost subscription into a recovered one.

<Note>
  There are **two** mandate code paths in the backend. The customer-facing portal creates account-level mandates (`AccountMandate`, keyed to the global account), the flow documented here. There is also a merchant-side `DirectDebitService` that creates a customer-scoped mandate as a `PaymentMethod` on a single tenant. Both talk to the same Nomba e-mandate API and share the same `PENDING → ACTIVE` normalisation.
</Note>

Next: the [wallet](/identity/wallet) and [cards](/payments/cards) that round out a customer's global account, or the [dunning engine](/billing/dunning) that charges the mandate.
