payment-flow-states-reference
Pure-reference catalog of payment lifecycle state machines across Stripe, Adyen, PayPal, and Braintree. Covers the canonical states (created / requires_action / processing / succeeded / requires_capture / canceled / failed), authorisation vs capture (separate vs combined), the asynchronous webhook states, refund / dispute / chargeback transitions, and the per-platform terminology mapping (Stripe PaymentIntent.status vs Adyen Authorisation/Capture vs PayPal Order). Use when designing tests for payment flows or auditing state-handling code. Composes 3ds-test-flow-reference.
payment-flow-states-reference
Overview
Every payment platform exposes a state machine - the PaymentIntent in Stripe, the Authorisation in Adyen, the Order in PayPal, the Transaction in Braintree. Each has different terminology for what is fundamentally the same lifecycle.
Per stripe.com/docs/payments/payment-intents: "The PaymentIntent encapsulates the lifecycle of a customer payment."
When to use
The canonical states
Most payment systems share the same conceptual states:
| State | Stripe | Adyen | PayPal | Braintree |
|---|---|---|---|---|
| Created | requires_payment_method | Received | CREATED | created |
| Awaiting action (3DS, etc.) | requires_action | RedirectShopper | PAYER_ACTION_REQUIRED | n/a (handled inline) |
| Processing | processing | Pending | PENDING | submitted_for_settlement |
| Authorized (not captured) | requires_capture | Authorised | APPROVED (no immediate capture) | authorized |
| Captured / succeeded | succeeded | [Capture] Settled | COMPLETED | settled |
| Failed | failed (charge) | Refused | DECLINED | gateway_rejected / failed |
| Cancelled | canceled | Cancelled | VOIDED | voided |
| Refunded | succeeded + refund object | [Refund] Settled | REFUNDED | refunded |
| Disputed / chargeback | disputed (in dispute object) | [Chargeback] | dispute | disputed |
Authorisation vs capture
Two-step:
Default in most systems is auto-capture (auth + capture in one call). Separate auth-then-capture is used for:
Per stripe.com/docs/payments/capture: PaymentIntent with capture_method=manual requires explicit capture call.
Webhook event sequence
The webhook events emitted per state transition. Stripe example:
1. customer.created
2. payment_intent.created
3. payment_intent.requires_action (if 3DS)
4. payment_intent.processing
5. payment_intent.succeeded
AND
charge.succeededThe asynchronous nature means tests must wait for webhook arrival, not rely on synchronous API return.
Refund states
captured payment
↓
refund created (status: pending)
↓
refund succeeded (or failed)Per stripe.com/docs/refunds: refunds are async; the API call returns immediately with pending, then a webhook delivers the final state minutes to hours later.
Dispute / chargeback states
The most-complex part of the state machine. Per Visa's chargeback reason codes (cite by stable ID: Visa Chargeback Reason Codes), the customer's bank initiates the chargeback; the merchant has a fixed window to respond.
| State | Meaning |
|---|---|
| Inquiry | Bank requests info; not yet a chargeback |
| Pre-arbitration | Initial dispute filed |
| Won | Merchant evidence accepted |
| Lost | Merchant evidence rejected; funds returned to customer |
| Pre-arbitration accepted | Merchant accepts the loss |
Per stripe.com/docs/disputes: disputes resolve over weeks; test scenarios use Stripe's test- mode dispute API to trigger transitions synchronously.
Idempotency
Most payment APIs accept an Idempotency-Key header (Stripe, Adyen) or equivalent. The pattern: retry with the same key produces the same response.
Per stripe.com/docs/api/idempotent_requests: "Stripe supports idempotency for safely retrying requests without accidentally performing the same operation twice."
Tests should verify the merchant code uses idempotency keys for every mutating call.
State-handling test surface
| Surface | Test |
|---|---|
| Created → succeeded (happy path) | Standard test-card; assert each state observed |
| Requires-action (3DS) | Per 3ds-test-flow-reference |
| Failed (insufficient funds) | Use insufficient-funds test card; assert state |
| Cancelled before capture | Manual-capture + cancel; assert state |
| Webhook idempotency | Replay webhook twice; assert idempotent handling |
| Refund full | Capture + full refund; assert state sequence |
| Refund partial | Capture + partial refund; assert state |
| Dispute won | Trigger dispute; respond; assert won |
| Dispute lost | Trigger dispute; don't respond; assert lost |
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Treating the API return as the final state | Async; succeeded comes later | Wait for webhook |
| No idempotency key | Network retries duplicate-charge customers | Always set idempotency |
| Hardcoded sleep waiting for webhooks | Flaky | Poll webhook endpoint or queue with timeout |
| Skipping the requires-action flow | 3DS regulations require it for most EU cards | Always test 3DS path |
| Stale state stored locally | Local DB diverges from platform | Webhook-driven update |
| Trust the request-body status | Webhooks can be replayed by attackers | Verify signature + idempotency |
| One test for all platforms | State terminology differs | Per-platform test suite |
| Refund tests in sync flow | Refunds are async | Webhook-based |