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

# Wallet Top-Up

> Fund the wallet in advance through Duro's inline Nomba checkout — real money, real card, and the card is saved for every merchant on the way through.

A top-up is how a customer puts money into their [wallet](/identity/wallet) before a renewal needs it. It's a first-class checkout with its own kind — `topup` — that opens Duro's **inline Nomba checkout** for any amount the customer chooses, and on payment credits the global wallet. This is the one checkout flow that is **live Nomba, no sandbox**: real cards, real money in.

## The flow

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as Customer (portal)
    participant API as public-api (/portal/email)
    participant CO as Inline checkout (Nomba)
    participant WH as Nomba webhook → worker
    participant W as Wallet (core)

    C->>API: POST /wallet/topup { amount }  (naira, min 100)
    API->>API: create checkout_session kind=topup (kobo)
    API-->>C: { checkoutToken, checkoutUrl }
    C->>CO: open inline Nomba checkout
    CO-->>C: pay ₦ amount with card
    CO->>WH: payment_success (online_checkout)
    WH->>W: credit wallet (reason: topup, ref = nomba txn id)
    WH->>W: save card to account (tokenised, deduped)
    W-->>C: balance updated · card on file
```

1. From the portal, the customer calls `POST /wallet/topup` with a naira `amount` (integer, **min ₦100**). The service creates a `topup` [checkout session](/payments/checkout) with the amount converted to **kobo** and returns a `checkoutToken` + `checkoutUrl`.
2. The portal opens that token in the same **inline Nomba checkout** used everywhere else — the customer pays an arbitrary amount with their card.
3. Nomba fires an `online_checkout` `payment_success` webhook. The worker settles it: it **credits the wallet** with `reason: topup`, keyed idempotently to the Nomba transaction id, and **saves the card** to the account.

## Value is granted on the webhook, not the browser

A top-up settles the same way every Duro payment does: the browser callback is a UI hint, and the **webhook is the trigger**. The wallet is credited when the settlement runs, keyed to the Nomba transaction id — so a closed tab, a replayed callback, or a duplicate webhook never double-credits and never *fails* to credit. In the hardened path Duro **never grants value on a webhook alone** without the truth being confirmable; a re-query against Nomba is available to reconcile a delivery before crediting.

<Note>
  Amounts cross a unit boundary here. The portal API takes **naira** (`amount: 100` means ₦100, the floor); the checkout session and the wallet store **kobo** (`10000`). The `× 100` conversion happens once, server-side, when the session is created.
</Note>

## Every top-up also saves the card

Because a top-up is a checkout where a real card is charged, it doubles as card capture. When the settlement sees a tokenised card on the payment it **saves it to the account** — deduped by the Nomba token key, so paying twice with the same card yields one saved card, not two. This is the mechanism behind **"Add a card"** in the portal: adding a card is simply a **minimum ₦100 top-up** that tokenises the card and credits the wallet with the ₦100. There is no separate "save a card without paying" path — the smallest real charge captures the card and funds the balance at the same time. [See global cards →](/payments/cards)

## Bounds and attachment

* **Range.** The amount must be an integer between **₦100 and ₦5,000,000**; anything outside is rejected before a session is created.
* **A merchant to attach to.** A checkout session is tenant-scoped, so the top-up is attached to one of the merchants the customer already subscribes to. A brand-new email with no subscription anywhere has no merchant to hang the session on and can't top up yet.

Next: [global cards](/payments/cards) — the saved card this flow captures, reusable at every merchant.
