The payment lifecycle

Every status, what moves between them, and which ones are terminal.

States

| 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 terminal

This 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:

  1. Ship on paid. Fastest. You are accepting reversal risk knowingly, which for a low-value digital good is usually correct.
  2. Ship on settled. The money has left escrow; a reorg that deep is no longer possible. Slower.
  3. Ship on 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 '{}'

Under- and overpayment

Customers send the wrong amount. It is not rare.

Both raise events. Both are reachable in test mode.

Expiry

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.

Reading it

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.