Skip to main content
When a card is declined during an Apple Pay or Google Pay purchase, the wallet sheet closes with only the wallet’s generic failure state — for example, Apple Pay shows “Order failed” with no explanation. Without a visible decline reason, buyers tend to retry the same card several times, which lowers their chance of a successful purchase. Crossmint always delivers a structured decline reason to your page. This guide shows the two ways to surface it, and how to tell a decline apart from a user cancellation.

Prerequisites

Option 1: Enable the Built-In Error Banner

The checkout component includes an error banner that displays the decline reason. On the Apple Pay and Google Pay screens this banner is hidden by default. Enable it with one appearance rule — appearance.rules.GlobalMessage.display: "visible":
With this rule set, the checkout shows the decline reason above the payment button after the wallet sheet closes.
The built-in banner dismisses automatically after 8 seconds. If your page constrains the checkout iframe’s height, the banner can render outside the visible area. For full control over placement and persistence, use Option 2.

Option 2: Render Your Own Error Message

The checkout reports every order change to your page. After a decline, the updated order contains payment.failureReason. Read it with the useCrossmintCheckout hook and render the message anywhere on your page:
Place the component where the buyer is guaranteed to see it — directly adjacent to the checkout, or in a sticky status region. Do not place it where it can fall outside the visible viewport. Keep listening after the sheet closes. With Google Pay, the sheet closes as soon as the buyer authorizes, and the decline can arrive afterwards. Some declines also arrive asynchronously while the order is finalized. React to failureReason whenever it appears, and clear your error UI when the buyer starts a new attempt.

Tell a Cancellation Apart from a Decline

The sheet closing is ambiguous on its own: it happens both when the buyer cancels and when the card is declined. Use the order state to distinguish the two: A cancellation never produces a failureReason, and a decline always does. Gate your error UI strictly on failureReason — never on the sheet closing.

The failureReason Object

Common code values include insufficient_funds, expired_card, incorrect_cvc, incorrect_number, do_not_honor, card_not_supported, authentication_required, issuer_unavailable, duplicate_transaction, and generic. Codes that do not map to a specific reason collapse to generic. Most declines return the order to the awaiting-payment state, so the buyer can retry — ideally with a different card or payment method, as the message suggests where relevant. Some declines are terminal and leave the order in a failed state; in those cases the order does not return to awaiting-payment and a retry on the same order is not possible. Gate any “retry” affordance on the order returning to awaiting-payment rather than assuming every decline is retryable, and never retry a declined payment automatically on the buyer’s behalf.

Common Gotchas

The checkout iframe’s height is managed dynamically by the SDK. If your page wraps the iframe in a fixed-height container with internal scrolling, content near the bottom — including the built-in error banner — can land below the fold. Let the SDK manage the iframe height, or render your own error message outside the iframe (Option 2).
This is expected, particularly with Google Pay, where authorization completes before the charge is attempted. Keep observing order updates after the sheet closes instead of checking once.

Next Steps

React Hooks

UI Customization

Payment Methods