Transactions
Accounts → Transactions is “somebody has given you a reference. This takes you to it.”
There is no transaction list, deliberately
Section titled “There is no transaction list, deliberately”The provider API has no operation that enumerates transactions. There are three ways to a payment:
- A reference, on this page.
- The merchant’s own portal list, which is theirs.
- The risk review queue, for payments that were held.
So this page is a lookup rather than a table, and that is the honest shape — a search box over a list that does not exist would have nothing to show before the first query and nothing to page through after it. The page says so itself: “browsing transactions is not possible from here.”
This is a design decision, not a gap. Staff do not need to browse other businesses’ payment traffic; they need to answer a question about one payment that somebody has raised.
The lookup
Section titled “The lookup”| Field | Notes |
|---|---|
| Merchant reference | “The reference the merchant gave the payment. Searched across every merchant, over the last two years.” e.g. PSG-812400. |
No match says what the limits are: “the lookup covers the last two years. A payment older than that, or a reference from a different system, will not be here.”
The header
Section titled “The header”Once a payment is found, its facts sit above the tabs:
| Fact | Notes |
|---|---|
| Transaction | The id and reference. |
| Merchant | The merchant’s name and code (the id, if the name can’t be read). |
| Partner | The partner’s name and code: “The partner that carried it — after a failover, the one that took over.” Absent until a partner has been chosen. Each attempt’s own partner is listed further down. |
| Partner’s own id | “The value to quote when escalating to the partner.” |
| Gross / Customer paid / Merchant received | The three figures. |
| Counterparty | — |
| Created / Completed | — |
| Why it failed | When it did. |
Partner status
Section titled “Partner status”A Partner status strip above the tabs shows the latest partner status check,
in words, and when it ran. Check with partner (transaction.retry, audited)
asks the payment’s partner again and always answers:
- In flight (or
expired): through the same call and guarded transitions as the status poller. A payment the partner confirms is settled, one it declines fails, and anything else changes nothing. - Final, and reached a partner: a read-only partner status read, compared with our record: “… confirms the payment succeeded. Our record matches.” A disagreement is reported (“… reports succeeded, but this payment is recorded as failed. It is recorded as a money exception for staff to review.”), logged with the partner’s raw answer, and recorded as a money exception. It is never applied automatically. An answer we can’t read changes nothing: “… answered with a status we could not read, so nothing was changed. Try the check again later; if it keeps happening, raise a correction.”
- Never sent (
initiated, or closed before reaching a partner): explains that nothing was charged. One exception is a payment left behind by the old timeout handling, below. If it has an attempt the partner never answered, the check records that partner and asks it, and a collection it confirms is settled, even one showingexpired.
An unanswered partner call is a submitted one. A timeout, a 5xx or an
unreadable reply from the partner is recorded as pending with that partner, for
collections, payouts and refunds. The timeline notes “Sent to {partner}; no
answer in time, so its status is being checked with them”. The poller, webhooks
and status checks then follow it as usual. Before, such a row stayed initiated
with no partner. A collection the customer approved was swept as expired before
reaching a partner, and a payout could be auto-reversed as never sent although
the partner had paid it.
A payout or refund call that began is never auto-reversed. We record that the call is starting, and to which partner, before making it. If the worker then dies mid-call and no answer is recorded, the row isn’t reversed as never sent. After 15 minutes it appears in the stranded-outbound report for a person to settle against the partner’s records. A partner’s answer is recorded even if the merchant’s own HTTP client has disconnected.
No amount, no settlement, on live. A live collection the partner confirms without reporting an amount is not settled, because the amount can’t be checked against what was asked. It stays in flight, the status check asks the partner for the amount, and if none ever comes it ends in reconciliation. Test collections are unchanged: the sandbox reports no amounts.
A refund is asked about as a refund. Where a partner keeps refunds as their own records, as Paystack does, the poller and status checks ask for the refund by the partner’s refund id, not as a payout. A refund the partner can’t find, or one we hold no partner refund id for, stays in flight for a person to trace on the partner’s dashboard. It is never failed automatically, since that would return the money to the merchant while the partner may still be refunding the customer. See Paystack.
The answer is recorded in words, for example “paypartner_payswitch confirmed the
payment succeeded. It has been settled.” Merchants have the same button on
their transaction sheet. The check is queued and run by the gateway worker’s
partner-status-checks job within seconds. If the gateway worker is down, the
strip stays at Asking the payment partner….
From the API: POST /v1/transactions/{id}/status-checks (transaction.retry)
and GET /v1/transactions/{id}/status-checks/latest (transaction.read).
Five tabs, five questions
Section titled “Five tabs, five questions”| Tab | The question it answers |
|---|---|
| Attempts | Why did it go to that partner, and what did they say? |
| Timeline | What happened to it, in order, and who did it? |
| Risk | Did a rule touch this payment, and did it actually act? |
| Ledger | Where did the money go, and does it balance? |
| Disputes | Is somebody claiming it back? |
Each reads when you first open it. They are tabs rather than a stack because a support conversation is about one of them at a time.
Attempts
Section titled “Attempts”“Each try, which partner took it, and what they said back.”
| Fact | Notes |
|---|---|
| Partner’s own id | “The value to quote when escalating to them.” |
| Partner reference | — |
| Idempotency key | “What we sent them. This is the evidence that a repeat was not a second charge.” |
| Chosen by rule | “The answer to ‘why did it go there’.” |
| Started / Finished | Finished is the time it was done. Waiting for the result means the payment is still in flight. No answer recorded means the payment has an outcome but this call never got one. |
An attempt shows the payment’s final outcome, not the partner’s first reply.
A mobile-money prompt is answered pending at once. When the payment then
succeeds, fails, expires or is cancelled, the latest attempt with the partner
that carried it takes the same status and finish time. A late success on an
expired prompt turns that attempt succeeded. A call answered on the spot is
finished at once. Later refunds and reversals don’t change the attempt, and
attempts with partners the payment failed over from keep their own answer.
This applies since 000088, which also filled in older rows: a refunded or
reversed payment’s attempt reads succeeded.
No attempt recorded is itself an answer: “a payment with no attempt never reached a partner — it was refused by us, held for review, or is still queued.” Check Risk review before escalating to a partner who has never seen it.
Reading the payloads
Section titled “Reading the payloads”What we sent the partner and what they returned, including the HTTP status and the failure message.
Timeline
Section titled “Timeline”Every state change in order, with who or what caused it. This is the tab for “the merchant says it succeeded and then failed”.
Whether a rule touched this payment — and, crucially, whether it acted. A shadow-mode rule appears here having done nothing. See Risk.
Ledger
Section titled “Ledger”The journal entries for this payment. If the money is somewhere unexpected, this is where it says so, and the ledger balances or the write failed — see Ledger.
Disputes
Section titled “Disputes”Any case against this payment. See Disputes.
Corrections
Section titled “Corrections”Accounts → Corrections (/transactions/corrections, transaction.read) is
“Payments merchants believe are recorded wrongly. A partner check ran when each
was raised.” A merchant raises one from their transaction sheet:
| What the merchant says | Shown here as |
|---|---|
paid_not_recorded |
Paid, but not recorded as succeeded |
recorded_not_paid |
Recorded as succeeded, but not paid |
wrong_amount |
Wrong amount recorded |
other |
Something else |
A payment can have one open request at a time. Raising one queues a partner check,
so a payment the partner confirms is already settled when you open it. It also
emails staff who hold transaction.retry, for live requests only, so sandbox
experiments don’t alert the payments team. Test requests still appear here, marked
Test, and can be decided. The merchant is emailed the decision either way. The subject is
“Correction requested: {merchant} — {reference}”. The merchant’s own words
stay in the console, not the email.
Show: Open (the default, “Waiting for a decision”), Resolved, Not upheld, or All. Each request shows the reference, a Test pill where it applies, its state, Merchant says, Evidence, the partner check and, once decided, Decision.
Decide (transaction.retry, audited) asks for:
| Field | Notes |
|---|---|
| Decision | Resolved — the merchant was right, or Not upheld. |
| What was found and done | “The merchant reads this, word for word.” |
“This closes the request and emails the merchant your explanation. It moves no money: a payment the partner confirms is settled by the status check, or with Settle on evidence if we had closed it as failed.” So fix the money first, through Settle on evidence, Reconciliation or the ledger, then decide. An open paid but not recorded request has Settle on evidence on its card.
A request open longer than an hour raises an operations alert.
From the API: GET /v1/transaction-corrections with status, transaction_id,
limit and offset (paged, with has_more), and
POST /v1/transaction-corrections/{id}/decide with decision (resolved or
rejected) and resolution.
Money exceptions
Section titled “Money exceptions”Accounts → Money exceptions (/transactions/exceptions, transaction.read): “Payments a partner
reports differently from us. Open ones raise an alert until they are resolved.”
The gateway records one wherever that happens:
| Kind | Shown as | When |
|---|---|---|
partner_paid_not_settled |
Partner says paid; merchant not credited | The partner says succeeded, but we closed it as failed or cancelled; or, on live, the partner gave no amount or a different one; or the success lost a race to a failure. |
partner_disagrees |
Partner contradicts our final record | A status check found the partner contradicting a final record. |
partner_reversed |
Partner reports a reversal | A poll or callback reports a reversal or refund. |
There is one open row per payment and kind; repeats raise its occurrences. Show switches between Needs a person (open only, the default) and all.
An exception closes itself when the payment later settles, with a resolution
starting “Settled:” and no person named. Otherwise Resolve
(transaction.retry, audited) records “What was found and done” (at least 12
characters): “Records what was found and done. It moves no money: to credit a
payment the partner confirms, settle it on evidence first.” On a
partner_paid_not_settled row whose payment is failed or cancelled, the row also
offers Settle on evidence.
Every open exception raises an operations alert until it is resolved.
From the API: GET /v1/money-exceptions?open_only=true and
POST /v1/money-exceptions/{id}/resolve with resolution.
Settle on evidence
Section titled “Settle on evidence”“For a collection we closed as failed or cancelled that the customer paid. The
partner is asked again; the payment is credited to the merchant only if it
confirms the success at our amount.” Only collections, and only failed or
cancelled ones.
| Field | Notes |
|---|---|
| Why it is believed paid | “The customer’s debit message, the partner’s statement line, the correction request.” |
| Authorised by | A second operator. Naming a colleague holds it: “Nothing happens until the approver you named approves it in Approvals. Then the partner is asked again.” While yours is the only active staff account you may name yourself, recorded as self-authorised. |
Ask the partner and settle queues a check. The gateway asks the partner again and settles (credits the merchant) only if it confirms the success and the amount agrees. Otherwise nothing changes and the check’s outcome says why. A negative partner fee is never taken as agreement. Nothing is credited on your word alone.
From the API: POST /v1/transactions/{id}/settle-on-evidence with reason and
approved_by (transaction.retry).
Answering “where is my payment”
Section titled “Answering “where is my payment””In this order, because each step rules out the one after it:
- Look it up by reference. If nothing carries it, the charge was never created — that is the merchant’s integration, not our platform.
- Read the header. Status and “why it failed” settle most questions on their
own. If it’s still in flight or
expiredand the customer says they paid, Check with partner first. - Attempts. No attempt means it never left us; an attempt with a partner failure means it did, and the partner’s own id is what you quote to them.
- Risk. A held or refused payment is explained here, and the merchant cannot see it.
- Ledger, if the money moved but the balance looks wrong.
- Payloads, last and only if the partner’s behaviour is the question.

