The most dangerous checkout result is often “we do not know.” A payment request can succeed at the provider while its response disappears. Retrying with a new identity may charge twice; treating the timeout as a decline may release stock for an order that was paid.
This lesson turns failure cases into explicit recovery rules. You will distinguish a retry from a new purchase, identify what idempotency protects, and record the trade-offs that remain after the happy path works.
In Process Orders Asynchronously Without Losing Work, Fieldnote created durable packing and receipt jobs after acceptance. This lesson also stands alone: assume a pending order and stock hold are committed before an external payment call, with a second local transaction accepting the paid order. The route hub connects that design to the earlier structural and sequence views.
Give a retry the same identity
The shopper intends to buy two notebooks once. A double click, a browser retry, and a lost response are three deliveries of that intent, not three purchases.
Fieldnote scopes checkout-key-104 to the authenticated caller and checkout operation. It stores the key, a normalized request fingerprint, the operation's state, and references to its order and payment attempt. A unique database constraint prevents two concurrent requests from claiming that same identity independently.
A matching retry reads or resumes the existing operation. A different cart version or payment-method reference under the same key produces 409 IDEMPOTENCY_CONFLICT. A deliberate new purchase needs a new key, after the earlier ambiguous attempt has been resolved. Hashing the cart alone is insufficient: the same shopper might legitimately buy the same notebooks twice.
The payment adapter uses its own persisted provider key, fieldnote/pay_104, for that one payment attempt. Browser retry keys and provider operation keys protect different boundaries. Keep the mapping durable rather than constructing a fresh provider key each time a request handler runs.
This follows the request-identity approach discussed in AWS's guidance on idempotent APIs. The implementation still needs to make the identity claim and local state changes atomic; a key in an HTTP header is not sufficient by itself.
Promise the effect, then define the response
Idempotency concerns repeated effects. It does not require every API to return identical bytes forever. HTTP's definition describes the intended server effect of repeated requests; a checkout POST needs an explicit application contract to gain that protection.
Fieldnote returns pending status for an unresolved matching retry and stores the terminal response when processing completes. The authorized status resource shows the latest state. This is Fieldnote's policy, not a promise about a payment provider.
Provider policies vary. For example, Stripe's API v1 idempotency documentation describes saved first responses, parameter comparison, and key pruning after at least 24 hours. Those rules do not automatically become Fieldnote's rules or another provider's rules. Before integrating a real gateway, verify its exact API version, retention window, concurrent-request behavior, lookup facilities, and treatment of failures.
Fieldnote must retain enough operation identity to cover its supported retry and recovery period. After a provider's retry identity expires, blindly repeating an old payment may create a new effect. Reconcile through authoritative provider records or an operator workflow instead. An arbitrary cache timeout is not a business policy.
Walk the crash points
| Failure point | Durable evidence | Recovery rule |
|---|---|---|
| Before preparation commits | No new order, hold, or payment attempt | A matching retry may prepare the purchase |
| After preparation, before provider call | Pending order, hold, and stable payment identity | Claim and resume the same attempt under provider rules |
| After payment succeeds, before acceptance commits | Pending local state; provider may know success | Reconcile the saved attempt, then finalize locally |
| After acceptance commits, before browser receives it | Accepted order, terminal checkout result, outbox event | Matching retry returns the accepted result |
| After a handler creates a packing task, before worker progress is observed | Task and processed marker committed together | Redelivery finds the marker and creates no second task |
| After email gateway accepts, before sender records completion | Local job still appears incomplete | Deduplicate or query at the gateway if supported; otherwise duplicates remain possible |
A timeout does not select one row of this table for you. Recovery uses the persisted identity and an authoritative outcome, not the local exception message.
Make reconciliation a first-class path
A reconciler finds aged pending attempts and asks the provider about the saved payment identity. Verified success uses the same guarded local finalizer as the request handler: record payment, allocate stock, accept the order, and create its outbox event atomically. Verified decline releases the hold and cancels the pending order.
If the provider still reports a nonterminal result or cannot be reached, leave the payment pending and schedule another check. Alert operations staff when an attempt exceeds the shop's review threshold. Do not label an unknown payment failed merely to clear a dashboard.
This choice holds inventory while uncertainty lasts. That can prevent another shopper from buying scarce goods. Releasing earlier improves availability but risks a successful payment with no stock. A production team must choose an escalation and resolution policy for that tension; this route deliberately exposes it instead of claiming a universal timeout value.
Retries also consume capacity. Use bounded attempts, backoff, jitter, and a clear owner for each retry loop. Avoid multiplying retries independently in the browser, API client, payment adapter, and worker. AWS's timeout and retry guidance explains how retries can amplify load when a dependency is already struggling.
Live model · Sequence view
Recover an unknown payment outcome
A lost provider response leaves the pending order and stock hold unresolved. Matching retries reuse the saved checkout and provider identities. A reconciler obtains an authoritative result and invokes the same guarded finalizer as the request path. An age threshold prompts review rather than proving failure or authorizing a new charge.
Explore scenarios in this model
- Architecture view
- Checkout: preparation, payment and acceptance
- Accepted order: durable packing and receipt work
- Payment timeout: same identity, authoritative recovery
These links open the same public model in a selected view. They do not change its source.
Open this viewer on its own ↗ if the frame is unavailable. The explanation remains readable without the viewer.
Exploration is local to your viewer. Reload or Reset view returns to the source layout.
Know when compensation is a new operation
If an accepted order later cannot be fulfilled, a database rollback cannot erase a completed external payment. Fieldnote may need a refund, a stock release, and a customer notification. Each has its own identity, progress, and possible failure.
That is compensation: business work that addresses an earlier effect. It may not restore the exact original world, and it can itself need retry or human intervention. Microsoft's compensating transaction guidance explains these limits. A full cancellation-and-refund workflow is outside this five-lesson design; do not represent it as one magical “rollback” arrow.
Try it in Unograph
Add a second sequence flow for a lost payment response. Show the persisted pending state, the reconciler's provider lookup, and the guarded acceptance transaction. Annotate each retry with the identity it preserves. Add a note stating the inventory cost of keeping an unresolved hold.
Then remove the provider's idempotency guarantee as a thought experiment. If the diagram still claims safe automatic payment retries, its guarantee is too broad. Record what authoritative lookup or manual resolution would be required instead.
You now have a reviewable checkout design: a context boundary, module contracts, a synchronous trace, durable background work, and explicit recovery rules. Revisit Process Orders Asynchronously Without Losing Work to inspect external delivery limits, or return to Online Store Architecture: From Boundaries to Reliable Orders to choose a view to refine.