Payment Lifecycle
State diagram
Section titled “State diagram” ┌──────────────────────────┐ │ requires_payment_method │◄── (created) └─────────────┬────────────┘ │ payment method attached ▼ ┌──────────────────────────┐ │ requires_confirmation │ └─────────────┬────────────┘ │ confirm called ┌────────────┴─────────────────────┐ │ │ │ scheduled_payment_date │ no schedule / date = today │ is in the future │ ▼ ▼ ┌──────────────┐ ┌──────────────────┴──────────────┐ │ ready │ │ │ │ (scheduled) │ ▼ ▼ └──────┬───────┘ ┌────────────────┐ ┌────────────────────────┐ │ │ processing │ │ requires_action │ process_at │ │ (PSP pending) │ │ (3DS challenge) │ reached │ └───────┬────────┘ └────────────┬───────────┘ │ │ │ ▼ ┌──────┴──────┐ ┌──────┴──────┐ (joins processing ▼ ▼ ▼ ▼ flow above) ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │succeeded │ │ failed │ │succeeded │ │ failed │ └─────┬────┘ └──────────┘ └─────┬────┘ └──────────┘ │ full refund │ full refund ▼ ▼ ┌──────────┐ ┌──────────┐ │ refunded │ │ refunded │ └──────────┘ └──────────┘
Any non-terminal state → canceled (terminal)State descriptions
Section titled “State descriptions”requires_payment_method
Section titled “requires_payment_method”The initial state for all payment intents created via the API. No payment method has been attached. The intent stays here until a payment_method is provided at confirm time.
requires_confirmation
Section titled “requires_confirmation”A payment method has been attached but confirm has not been called. In most flows this state is transient — you attach the payment method and call confirm in the same step.
The payment intent has been confirmed with a future scheduled_payment_date. No money moves yet. The intent is queued — on the scheduled date, EnqueueScheduledPaymentIntentsJob picks it up and submits it for processing.
A ready intent can still be canceled before its scheduled date.
Canceling a payment that belongs to a payment plan skips that date, as Skip does in the dashboard. It’s refused with 400 invalid_status_for_cancel when the payment is dated today or earlier on your account’s clock, falls inside one of the plan’s suspension periods, belongs to a plan that is closing or closed, or has been claimed or sent for processing. Otherwise the payment is removed and the plan schedules again: a plan with a total collects the amount at the end. A refused cancel changes nothing and sends no payment_intent.canceled event. See Canceling a plan’s payment.
See Scheduled Payments for details on creating scheduled intents.
requires_action
Section titled “requires_action”The payment requires an additional customer action, typically a 3D Secure authentication challenge. The widget handles this automatically. The intent transitions to processing after the action completes, or failed if the customer abandons the challenge.
processing
Section titled “processing”The payment has been submitted to the PSP and is awaiting a final result. This state can last from milliseconds to several business days depending on the payment method (a bank debit clears about 2 business days after it’s sent).
If a card payment’s result doesn’t reach us, it stays processing while we confirm with the PSP whether the customer was charged. It then ends succeeded, or failed with processing_timeout. It isn’t charged twice in the meantime: a repeat confirm is refused until it’s settled.
succeeded
Section titled “succeeded”The payment completed successfully. Terminal state — the customer has been charged. Partial refunds keep the intent in succeeded; only a full refund transitions to refunded.
refunded
Section titled “refunded”All captured funds have been refunded. The intent transitions here automatically when the total refunded amount equals the original charge. Terminal state.
failed
Section titled “failed”The payment was declined or failed for another reason. Check the failure code for details. Terminal state — create a new payment intent to retry.
Two codes in last_payment_error mean the failure was on our side, not the card issuer’s. The customer wasn’t charged and their card is unaffected, so it’s safe to try again:
| Code | Meaning |
|---|---|
processing_error |
The payment couldn’t be sent to the PSP because of a problem on our side |
processing_timeout |
The payment timed out on our side and was cancelled |
canceled
Section titled “canceled”The payment intent was explicitly canceled. Terminal state.
What to build on
Section titled “What to build on”- Use webhooks, not polling. The synchronous confirm response may show
processingorready— do not assume success until you receivepayment_intent.succeeded. succeededis authoritative. Build your fulfillment logic on this webhook event.failedis permanent. Create a new payment intent to retry a failed payment.readyis not charged yet. Areadyresponse means the payment is scheduled, not collected. Monitorpayment_intent.succeededto confirm funds were captured.
Refund states
Section titled “Refund states”After a payment intent reaches succeeded, refunds are tracked as separate objects. A partial refund keeps the payment intent in succeeded. A full refund (total refunded equals original charge) transitions the payment intent to refunded. Use the refund object’s status (succeeded, pending, failed) and the payment intent’s status to track state.