State reference

Payment states

Payment states mirror the underlying provider (card processor, Net30 for invoice). Throttle normalises them so your handler is provider- agnostic.

State machine

stateDiagram-v2
  [*] --> pending: POST /payments or buyer click
  pending --> processing: provider needs more time (3DS, redirect, bank debit)
  pending --> authorized: Provider/Net30 authorizes
  pending --> failed: provider declines
  processing --> authorized: challenge cleared
  processing --> captured: settled
  processing --> failed: challenge abandoned or declined
  authorized --> captured: capture call
  authorized --> voided: void before capture
  authorized --> expired: authorization outlives its TTL
  captured --> refunded: full refund
  captured --> partially_refunded: partial refund
  partially_refunded --> refunded: subsequent refunds total to full
  refunded --> [*]
  voided --> [*]
  expired --> [*]
  failed --> [*]
Payment lifecycle from creation to terminal.

States

  • pending — payment row created, no provider response yet. UI shows a spinner; back-end has the row id but no auth/capture proof.
  • processing — the provider has neither approved nor declined yet: the buyer is mid-3DS challenge, away on a redirect method, or the debit is still settling. Nothing has failed, so there is no error.code — treat this as "keep waiting", not as a decline. Fires payment.processing, and a resolution event (payment.authorized, payment.captured or payment.failed) always follows.
  • authorized — provider authorised the amount (funds reserved). Most card flows auto-capture so this is brief; manual-capture flows stay here until the merchant explicitly captures.
  • captured — funds moved. Triggers payment.captured outbound event. Order can now transition to paid. Capture may be partial: pass an amount below the authorized total to POST /v1/payments/{id}/capture. The captured value is tracked on capturedAmount (the event carries both capturedAmount and authorizedAmount); the uncaptured remainder is released by the processor, and refunds are capped at the captured amount.
  • failed — provider declined OR a network/timeout error during processing. Carries error.code + error.message. Terminal. A merchant can also declare it by hand with POST /payments/{id}/mark-failed — the promised bank transfer that never arrived, the cheque that bounced — which writes error.code: "manually_marked_failed" and the supplied reason. Only a pending, processing or already- failed payment is failable by hand; anything resolved returns 422 payment_not_failable.
  • voided — authorisation released before capture. Funds never moved. Terminal.
  • expired — the authorization outlived its usable life and was never captured, so the hold has lapsed at the provider. Throttle sweeps these itself because providers do not reliably notify on expiry, and fires payment.expired when it does. To collect this money you must charge again; capture will not work. Terminal.
  • partially_refunded — at least one refund processed but total refunded is less than captured amount. Still accepts further refunds.
  • refunded — total refunded equals captured amount. Terminal.

Refunds

  • POST /v1/payments/{id}/refund returns money from one payment. POST /v1/orders/{id}/refund works on the order: when an order was paid more than once — a deposit and a balance, an instalment plan, a retried charge that settled on a second row — the refund is spread across its captured payments, newest capture first, and amount may exceed any single one of them. Omit amount to return everything still refundable on the order.
  • Asking for more than remains returns 400 refund_amount_exceeds_balance, whose message names the amount that is still refundable. The check runs before any money moves, so a refund that cannot be completed in full sends none of it.
  • A refund claims its amount before the provider is called, so a second refund of the same payment arriving at the same moment gets 409 refund_already_in_progress rather than passing a stale balance check and returning the money twice. Wait for the first to resolve, then re-read what is refundable.

Net30 special cases

  • Net30 payments go through pendingauthorized on invoice issue, then → captured when the buyer pays the invoice (manual mark-paid OR ACH webhook).
  • invoice.overdue fires when an authorized Net30 payment passes its dueDate; the payment row stays authorized until paid or voided.

Disputes & chargebacks

Disputes are tracked out-of-band from the status machine, on the disputed boolean plus disputeReason / disputeOpenedAt (all exposed on the payment object). A payment keeps its underlying status (usually captured) while disputed.

  • Card chargebacks are ingested automatically from the payment provider: an inbound dispute.* / chargeback.* webhook sets disputed = true and fires payment.disputed. A merchant-favourable resolution (won / reversed) clears the flag and fires payment.dispute_cleared.
  • Net30 disputes are opened/cleared manually from the dashboard and fire the same two events.