A money-movement state should tell you what the system knows about a transaction and which actions are still legal. Model it as a set of explicit, validated transitions—not as status strings that unrelated API handlers can update independently. Keep provider-specific statuses at the integration boundary, preserve unknown outcomes, and reconcile external results before repeating uncertain financial effects.
What should a payment state mean?
A state is a business assertion, not merely a label. For example, “authorized” means the payment method has been approved for a specified amount, but the funds have not necessarily been captured. That state permits some actions and rules out others. A useful model therefore pairs each state with its legal commands, known facts, and outstanding uncertainty.
As an Amazon Associate I earn from qualifying purchases.
There is no universal payment lifecycle that fits every provider, payment method, or rail. Treat the following as a domain-model starting point, then adapt it to the product and the provider contract.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match| Domain state | What the system knows | Typical legal next actions |
|---|---|---|
created |
A payment request exists; no provider outcome is yet recorded. | Submit for payment, cancel locally if not submitted. |
requires_payment_method |
No usable payment method has been attached or the previous method was declined. | Attach or replace the method, cancel. |
requires_customer_action |
Progress depends on a customer step such as authentication. | Resume after the action, expire under a backend-enforced policy, or cancel if the provider permits it. |
processing |
A provider operation is pending or its outcome is not yet known. | Wait, query, or reconcile; do not assume success or failure. |
authorized |
The payment is approved for an amount, but capture has not been recorded. | Capture within the authorized amount, void or cancel where supported, or await an applicable expiry. |
captured |
The capture operation is confirmed by the source of truth used by the system. | Handle a refund or later financial events as distinct operations. |
canceled |
The payment flow has been canceled and no further payment action is permitted by this model. | Start a new payment attempt if the customer or product needs one. |
failed |
The attempt has a known failure outcome and cannot proceed as-is. | Correct the cause or start a new attempt, with a new request identity. |
Do not force every financial event into one linear status field. Refunds, disputes, reversals, and settlement can require their own records or sub-lifecycles, linked to the original payment. Whether settlement belongs in your payment state model depends on the rail and provider: a captured payment and a settled payment are not necessarily the same business fact.
#1 Best Overall
How do I model a payment lifecycle?
Represent state changes as named commands or events handled by a central transition service. Each transition should validate its preconditions, update the payment and local financial records consistently, and record the decision. Handlers should request a transition rather than independently editing status columns.
| Command or event | Precondition | Result and financial effect |
|---|---|---|
SubmitPayment |
Payment is created, amount and currency are valid, and a usable payment method is present. | Record submission and move to processing or the applicable provider-mapped state; issue the provider request with a stable identity. |
CustomerActionCompleted |
The attempt is awaiting customer action and the received result matches that attempt. | Record the result and continue processing; do not infer capture from completion of authentication alone. |
PaymentAuthorized |
Provider result identifies the same payment and confirms authorization for a valid amount and currency. | Record the authorization and any corresponding hold; make capture or void eligibility explicit. |
CapturePayment |
Payment is authorized, capture has not already been confirmed, and the requested amount is within the authorized amount. | Request capture and record a confirmed capture only after authoritative confirmation; preserve an unknown outcome if the response is lost. |
CancelPayment |
Current state permits cancellation and no conflicting financial operation has won the race. | Request cancellation or void as appropriate; record the provider-confirmed result rather than assuming the request succeeded. |
PaymentFailed |
A definitive failure is associated with the current attempt. | Record the failure reason and make clear whether this attempt is terminal or can be corrected. |
For every transition, define its accepted source states, stable request identity, amount and currency constraints, local ledger or hold effect, persisted history entry, provider call, retry policy, timeout behavior, and emitted notifications. Use the same currency and amount invariants in API validation, transition logic, and ledger posting. Give each attempt a durable identifier so late provider results can be matched to the correct payment rather than applied to whichever attempt is current.
Protect state and money updates together
Use a database transaction and suitable concurrency control—such as optimistic version checks or row locking—to prevent competing commands from both winning. If capture and cancellation race, one transition must commit first; the other should re-read the current state and either reject, reconcile, or follow a documented compensating path. Do not emit a success notification before the state and corresponding local financial effects commit.
Free tools Windows power users keep installed
One-click scans. No signup required.
For asynchronous notifications, write an outbox record in the same local transaction as the state change. A separate worker can deliver it and retry delivery without losing the relationship between the committed transition and the message. Record meaningful milestone timestamps—such as submitted, authorized, captured, canceled, and reconciled—rather than overwriting one generic updated-at value.
Rank #3
How should I handle payment retries?
A retry must be recognized as the same logical request when it repeats an operation whose outcome may already have happened. Keep a stable internal request identity, and reuse it only with the same operation and parameters. A genuinely new attempt—for example, after a customer changes payment method—needs a new identity and a link to the prior attempt.
Stripe documents idempotency keys as a way to repeat requests without accidentally performing the same mutation twice. Its documentation says keys may be pruned after they are at least 24 hours old; reusing a key after pruning can result in a new request. That is a provider-specific retention rule, not a timeless duplicate-suppression guarantee. Retain your own request and operation history for as long as your system needs to prevent or investigate duplicate effects, and check the current provider behavior for the API version and payment method you use.
Rank #4
- Persist the internal operation identity and parameter fingerprint before sending a provider request.
- On a retry, verify that the identity maps to the same operation, payment, amount, and currency.
- Use the provider’s idempotency mechanism where available, but do not rely on it as the only record of whether your business operation was attempted.
- For a known definitive failure, apply the documented recovery path. For an unknown result, reconcile before issuing a new non-idempotent operation.
What happens when a payment times out?
A timeout means your caller did not receive a timely answer; it does not prove that the provider did not perform the operation. Preserve this uncertainty explicitly, for example as processing or an internal outcome-unknown condition, rather than changing the payment to failed. Query the provider or reconcile through its event and reporting mechanisms before deciding whether a repeat request is safe.
- Record the timeout. Keep the operation identity, parameters, attempt time, and last known state. Do not record a success or definitive failure without evidence.
- Check for an authoritative result. Query the provider or wait for a correlated webhook. Match the result to the payment and operation, not just to a customer or order.
- Apply the result through a transition. Validate that it is legal from the current state and record it in the transition history.
- Retry only if safe. If the provider confirms the operation did not happen, retry according to its rules. If the result remains unknown, use the same logical request identity where supported and avoid creating a second financial effect.
- Escalate unresolved cases. Keep the payment out of a false terminal state and make the unresolved operation visible for automated or operator reconciliation.
Backend-enforced expiration also needs a defined race policy. A client-side countdown is not authoritative: a delayed provider success can arrive after the customer-facing timer expires. Decide whether that result can complete the payment, requires a compensating action, or needs operator review, and apply the same rule to webhooks, polling, and manual corrections.
Best Value
- Used Book in Good Condition
How should webhooks and reconciliation update state?
Treat provider webhooks, polling results, and reconciliation records as inputs to the same transition rules—not as permission to write arbitrary values into a status column. Persist incoming event identifiers and processing outcomes so duplicate delivery can be detected. Correlate each event to the provider object and internal attempt it concerns.
- Duplicate events: recognize already-processed event identities and avoid repeating ledger postings or notifications.
- Out-of-order events: validate whether the incoming fact is consistent with current state; do not move a payment backward merely because an older event arrived late.
- Late success after timeout: resolve the unknown operation using the documented race policy and the original attempt identity.
- Cancellation races: accept whichever authoritative operation wins, then reconcile or compensate for the conflicting request rather than pretending both succeeded.
- Operator corrections: record who made the correction, its reason, evidence, and resulting transition; avoid silently rewriting history.
Reconciliation is the process that resolves differences between your recorded view and the provider or rail’s authoritative records. Make it possible to identify pending or mismatched operations, inspect their transition history, and safely replay processing logic without replaying a money movement that already occurred.
When does an orchestration workflow help?
A workflow orchestrator can coordinate asynchronous steps, retries, and error paths, but it cannot make an external payment effect infallible. Its retry semantics and execution history must fit the operation, and tasks that call providers still need stable request identities, duplicate-effect handling, and reconciliation.
Recommended Free Tools
AWS Step Functions distinguishes Standard and Express Workflows. AWS describes Standard Workflows as durable and auditable and says their execution history is available for up to 90 days after completion. Express Workflows are at-least-once, so task duplication must be safe. AWS also describes Standard Workflows as suitable for long-running workflows of up to one year. These are AWS service-specific properties; confirm current limits and semantics before choosing a workflow type.
| Choice | Documented characteristic | Design implication |
|---|---|---|
| Standard Workflow | AWS describes durable, auditable executions; history is retrievable for up to 90 days after completion. | Useful when durable orchestration and execution inspection matter; retain your own business history for any longer audit or reconciliation need. |
| Express Workflow | AWS documents at-least-once execution semantics. | Design task effects to tolerate duplicate execution and keep payment idempotency and reconciliation controls in the application. |
How do I check whether the model is safe?
- Can every state explain what is known and which commands are legal?
- Are authorization, capture, cancellation or void, refund, settlement, and dispute distinct where the product or rail requires them?
- Can every provider result be correlated to a specific internal payment attempt and operation?
- Do amount and currency invariants hold for partial amounts, captures, and refunds?
- Can a lost response remain uncertain until an authoritative result is found?
- Are concurrent commands, duplicate webhooks, late events, and retries handled without duplicate local postings or external effects?
- Can an operator reconstruct what happened from transition history and milestone timestamps?
- Does the chosen orchestrator’s retry and history behavior match the workload without replacing application-level controls?
Stripe’s PaymentIntent is a useful example of a provider lifecycle, not a complete internal domain model. Stripe says a PaymentIntent guides payment collection and recommends one per order or customer session. Its documented statuses include requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, and succeeded. Map such provider states into your own business concepts and legal transitions instead of exposing them as the whole application model. Exact behavior can vary by API version and payment method; verify the current provider reference before encoding state availability or timing assumptions.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




