Invoices & Bill Pay
- Base path
/api/portal/v1/wallet/me- Identity
- Publishable key (
pk_live_…) → OTP → customer session. The session belongs to the end user, not to the merchant, and reaches only that user's own data. - Authorization
- The session itself is the authorization; a user reaches only their own record.Step-up: for money-out actions the session alone is not enough; an OTP code or a trusted action token is required.
- Cross-surface
- The s2s API has NO invoice or bill-payment endpoint. The payment flow lives on the portal, invoice ISSUE and cancel in the panel (
POST invoices,POST invoices/:id/cancel—wallet:invoice:manage), and monitoring also in the panel (GET invoices,GET bill-payments—wallet:operation:read).
Two similar-sounding but distinct capabilities:
| Capability | What | Paid to |
|---|---|---|
| Invoice | A business inside the program invoices a user | A business inside the program |
| Bill pay | Paying a utility/telecom bill | An external biller |
In-wallet invoices
The contract for issuing, listing and paying invoices from the wallet is complete. It is awaiting release; these endpoints do not accept requests today. Status: Roadmap.
Issue an invoice
POST /api/panel/v1/wallet/invoices| Field | Type | Required | Description |
|---|---|---|---|
issuerAccountId | string | ✅ | Business account issuing the invoice |
currency | string | ✅ | |
lines | array | ✅ | Items: description, quantity, unitAmount |
totalAmount | string | — | Computed from lines when omitted |
payerWalletNo | string | ⚠️ | Wallet number of the payer |
payerPhone | string | ⚠️ | Phone of the payer |
externalInvoiceNo | string | — | Your invoice number |
dueAtIso | string | — | Due date |
notes | string | — |
⚠️ One of payerWalletNo or payerPhone is required.
If you omit totalAmount it is computed from the lines. If you send both they must be consistent — avoid rounding drift by doing minor-unit integer arithmetic.
The user sees and pays it
GET /api/portal/v1/wallet/me/invoices
GET /api/portal/v1/wallet/me/invoices/{id}
POST /api/portal/v1/wallet/me/invoices/{id}/payPayment comes from the wallet balance and may require OTP/step-up.
Webhooks
| Event | When |
|---|---|
wallet.invoice.issued | Invoice issued |
wallet.invoice.paid | Invoice paid |
wallet.invoice.cancelled | Invoice cancelled |
Bill pay
Inquiry, payment, status tracking and reconciliation are built. To run in production a biller integration must be connected: biller codes and debt inquiry come from it. Without one, these endpoints do not accept requests. Status: Roadmap.
Inquire the debt
POST /api/portal/v1/wallet/me/bills/inquire| Field | Required | Description |
|---|---|---|
billerCode | ✅ | Biller code |
subscriberNo | ✅ | Subscriber number |
The inquiry returns the amount due and, when available, the due date.
Pay
POST /api/portal/v1/wallet/me/bills/pay| Field | Required | Description |
|---|---|---|
billerCode, subscriberNo | ✅ | Values from the inquiry |
sourceWalletAccountId | ✅ | Account to pay from |
amount, currency | ✅ | |
billRef | — | Debt reference from the inquiry |
billerCategory | — | |
idempotencyKey | ✅ | Double-payment protection |
trustedActionToken | — | Step-up |
Track
GET /api/portal/v1/wallet/me/billsStatuses and reconciliation
Because bill pay depends on an external institution, it settles asynchronously:
| Event | Meaning |
|---|---|
wallet.billpay.dispatched | Instruction sent to the biller |
wallet.billpay.confirmed | The biller confirmed the payment |
wallet.billpay.failed | The biller rejected it; funds returned |
wallet.billpay.awaiting_recon | Outcome uncertain, sent to reconciliation |
wallet.billpay.reversed | A confirmed payment was reversed |
dispatched does not mean "the bill is paid". Tell the user "sent"; use "paid" only on confirmed. Otherwise, when the biller rejects, the user believes they have paid.
Never retry a bill payment sitting in awaiting_recon — it may already be paid on the biller side. Wait for reconciliation.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Inquiry returns nothing | Wrong subscriber number or biller code | Show a per-biller format hint to the user |
Payment stuck in awaiting_recon | The biller's response was inconclusive | Wait; do not pay again |
| Bill amount differs from paid amount | A partial payment was attempted | Whether partial payment is allowed depends on the biller integration |