Skip to main content
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; Duro creates a NIBSS e-mandate through Nomba, tracks it from PENDING to ACTIVE, and once active it becomes another rail the billing engine can charge on. Mandates hang off the same global account as the wallet and cards, so a mandate follows the person, not a single merchant.
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.

Creating a mandate

From the portal, the customer picks a bank, enters their account number and phone, and Duro does the rest:
  • 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:
  • 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 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.
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.
Next: the wallet and cards that round out a customer’s global account, or the dunning engine that charges the mandate.