A buyer reaches checkout, enters their card and gets “Payment failed.” The message does not tell them whether to correct a detail, finish bank authentication or stop because an order already exists. Stripe’s decline guidance assigns different next steps to those situations. A useful checkout preserves those differences without turning an internal error response into customer copy.
Start with the action the buyer can take, then write the message. The result should explain how to continue while staying within what the payment system actually knows.
Establish the payment state before writing the error
A successful network response is not the same as a successful purchase. Adyen explicitly warns that an HTTP 200 response can accompany an unsuccessful payment. Its payment result carries the relevant meaning. It even documents a refusal code of zero alongside an authorized result, where the refusal code should be ignored.
For a product team, the implication is simple: the message needs a defined payment state behind it. A generic error component cannot decide from the presence of a code alone whether money moved or whether the customer should submit again.
Stripe’s payment lifecycle also distinguishes a payment needing further action from one that has succeeded. An authentication step should lead into the available authentication flow. An already completed payment should lead to its confirmation. A screen that lost contact before learning the outcome needs a status check, not an invented decline reason. Use the pending-payment guidance when the result is still unknown.
Map a documented problem to one useful next action
The following examples apply Stripe’s published guidance to customer-present card checkout. They are suggested wording for a product team to adapt, not messages observed in a live store. The technical codes identify the source rule; they do not belong in the customer message.
| Documented Stripe condition | Suggested customer message | Action the interface should support |
|---|---|---|
incorrect_cvc |
“Check the security code on your card and enter it again.” | Return to the security-code input |
expired_card |
“This card has expired. Use a different card.” | Replace the payment method |
insufficient_funds |
“This payment could not be completed. Choose another payment method.” | Offer an available alternative |
authentication_required |
“Your bank needs you to confirm this payment.” | Open the supported authentication step |
generic_decline |
“This payment was declined. Contact your card issuer for more information.” | Keep a route back to payment selection |
duplicate_transaction |
“We’re checking whether this order has already been paid.” | Check the existing payment before offering another attempt |
The duplicate example assumes the merchant has actually started that check. If the interface cannot check automatically, say what it can do instead, such as directing the customer to an order-status page or support with an order reference. Do not promise a background action that has no implementation.
Keep the wording close to the action. A buyer asked to correct a security code should not have to hunt for the payment form. Someone being sent to their bank should understand why the checkout has opened another step. If there is no useful correction the merchant can identify, an elaborate explanation adds confidence without adding knowledge.
Do not expose the risk decision
Stripe says some sensitive reasons, including suspected fraud and lost or stolen cards, should be presented in the same manner as a generic decline. Adyen’s guidance is broader: do not expose the details of refusal reasons to shoppers.
That difference matters when a site uses more than one processor. A label that looks similar in two response dictionaries is not permission to show the same explanation. Keep the processor’s disclosure rules attached to the message mapping, and use the approved checkout component’s customer feedback where it handles the interaction.
The buyer does not need a suspicion or a raw rule name to understand the next available step. “Your payment was declined” can be appropriate even when the internal record contains more detail. Support staff may need a reference to investigate; the public screen needs an accurate action.
Make a retry a decision, not the default button
“Try again” is useful only when another attempt is the appropriate next step. It cannot fix an expired card, and it skips the question raised by a possible duplicate payment. Treat the action as part of the same state mapping as the message.
Consider a hypothetical shopper who presses Pay, sees no response and presses again. If the second screen presents a duplicate-transaction warning as an ordinary failure, the shopper may keep trying other cards. The useful recovery is to determine the status of the original order and show it. The screen must not say “You have not been charged” simply because its own request timed out.
Also preserve the commercial context. The customer should still know which order they are paying for and its current total after returning from authentication or choosing another method. If the order changed while they were away, show the changed commitment before asking them to pay.
Review the whole recovery path
For each supported state, walk through the message and its action together. Begin with a correctable input problem, then an authentication requirement, a declined payment with no detailed explanation and a possible duplicate. The review question is whether a buyer can reach the appropriate next state without guessing what the interface meant.
Record the outcome at the order level as well as the attempt level. One shopper may generate several payment attempts before completing a single order. A count of attempts that stop at an error does not, by itself, tell you how many buyers were lost.
The smallest useful message set is the one your checkout can support accurately. Give a clear correction when there is one, an alternative when it is available, and a status check when the outcome is unresolved. Each message should lead to something the system can actually do.
