Separate the order from the attempt
An order and a payment attempt answer different questions. The order describes what a customer wants; an attempt records an interaction with a provider. Treating them as separate concepts makes it easier to explain failed attempts, retries and refunds without losing the history of the purchase.
Name the uncertain states
Do not force every outcome into paid or unpaid. A request may still be processing, waiting for confirmation or requiring attention. Define what each state means and which actions are allowed. Make that meaning consistent across the customer screen, staff tools and notification emails.
Retry the same operation deliberately
Stripe supports idempotency keys for safely retrying supported API requests. Use a stable key for retries of the same intended operation, following the provider’s documented rules. Creating a new key on every network retry defeats that protection. Other providers need their own documented approach.
Reconcile instead of guessing
If a response is interrupted, check the provider’s record before presenting a final outcome or repeating a consequential operation. Store the provider identifier where staff can find it. Reconciliation should also detect disagreements between local payment state and the provider’s state.
Review the whole customer journey
Exercise interrupted requests, duplicate notifications and refund scenarios as well as a successful checkout. Decide what the customer sees while confirmation is pending. The engineering goal is a payment history that can be explained and recovered, not simply a successful API call.
