> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Display Payment Error Messages

> Show buyers why a card was declined after the Apple Pay or Google Pay sheet closes

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

* **Embedded Checkout**: An [Embedded Checkout](/payments/embedded/overview) integration with Apple Pay or Google Pay enabled. See the [payment methods guide](/payments/embedded/guides/payment-methods).
* **React SDK**: The code samples use `@crossmint/client-sdk-react-ui` with `CrossmintCheckoutProvider`. See [React Hooks](/payments/embedded/guides/hooks).

## 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"`**:

```tsx theme={null}
<CrossmintEmbeddedCheckout
    // ...other props
    appearance={{
        rules: {
            GlobalMessage: { display: "visible" },
        },
    }}
/>
```

With this rule set, the checkout shows the decline reason above the payment button after the wallet sheet closes.

<Note>
  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.
</Note>

## 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:

```tsx theme={null}
import { useCrossmintCheckout } from "@crossmint/client-sdk-react-ui";

function CheckoutErrorBanner() {
    const { order } = useCrossmintCheckout();
    const failureReason = order?.payment?.failureReason;

    if (failureReason == null) {
        return null;
    }
    return (
        <div role="alert" className="checkout-error">
            {failureReason.message ?? "Your payment was declined. Try a different payment method."}
        </div>
    );
}
```

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:

| Signal                                                           | Meaning               | What to show                                          |
| ---------------------------------------------------------------- | --------------------- | ----------------------------------------------------- |
| Sheet closes and `order.payment.failureReason` stays `undefined` | The buyer canceled    | Nothing, or a neutral "Payment not completed" message |
| An order update arrives with `order.payment.failureReason` set   | The card was declined | The decline explanation (`failureReason.message`)     |

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

| Field     | Type                | Description                                                        |
| --------- | ------------------- | ------------------------------------------------------------------ |
| `code`    | `string`            | Canonical decline code for logic, analytics, and support tooling   |
| `message` | `string` (optional) | User-safe explanation. **The only field safe to render to buyers** |

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

<AccordionGroup>
  <Accordion title="The error message renders but the buyer never sees it">
    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).
  </Accordion>

  <Accordion title="The decline arrives after the sheet is already closed">
    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.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={3}>
  <Card title="React Hooks" icon="react" href="/payments/embedded/guides/hooks" />

  <Card title="UI Customization" icon="palette" href="/payments/embedded/guides/ui-customization" />

  <Card title="Payment Methods" icon="credit-card" href="/payments/embedded/guides/payment-methods" />
</CardGroup>
