Every status, what moves between them, and which ones are terminal.
| Status | Means | Terminal | | --- | --- | --- | | requires_payment | Created. Nothing has arrived. | no | | detected | A matching transfer is in the mempool or a fresh block. | no | | confirming | Included, gathering confirmations. | no | | paid | Confirmed to the configured depth. | no — see below | | settled | Released from escrow to the merchant. | yes | | underpaid | Less arrived than was asked for. | no — a top-up can still complete it | | overpaid | More arrived than was asked for. | no | | expired | expires_at passed with nothing confirmed. | yes | | cancelled | You cancelled it. | yes | | reversed | A reorg undid a payment that had reached paid. | yes | | partially_refunded | Some of it went back. | no | | refunded | All of it went back. | yes |
paid is not terminalThis is the one that differs from a card gateway, and it is not a detail.
A blockchain can reorganise. If a reorg is deeper than the confirmation window that declared the payment paid, the transfer that paid it no longer exists — the payment becomes reversed and payment.reversed is emitted.
Three honest ways to handle it:
paid. Fastest. You are accepting reversal risk knowingly, which for a low-value digital good is usually correct.settled. The money has left escrow; a reorg that deep is no longer possible. Slower.paid, reconcile on reversed. Correct for anything reversible — an account credit, a subscription period, a licence.What is not honest is treating paid as final and having no payment.reversed handler at all. Rehearse it before you decide:
curl -X POST $PAYGATE/v1/test/payments/{id}/reorg \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" -d '{}'
Customers send the wrong amount. It is not rare.
underpaid — the payment stays open. A second transfer that brings the total to the asked amount completes it. amount_received tells you where it stands.overpaid — the payment is complete and there is a surplus. Refund it, credit it, or keep it; the gateway does not decide.Both raise events. Both are reachable in test mode.
expires_at is a unix timestamp. Set it at creation with expires_in (seconds).
A payment that expires with money already confirming is not abandoned — a late transfer raises payment.late_payment and the payment is completed rather than lost. A gateway that dropped it would be a gateway that keeps the customer's money and tells nobody.
Poll GET /v1/payments/{id} if you must; subscribe to webhooks if you can. The transactions behind a payment — hashes, confirmations, amounts — are at GET /v1/payments/{id}/transactions.