Skip to main content
When implementing custom layouts, you need to handle payment failures and display appropriate error messages to your users. This guide covers the available options and best practices.

Two types of errors

Before implementing error handling, it’s important to understand the difference between validation errors and payment failures: There’s also a third case — the SDK itself failing to start (e.g. an invalid client token) before any form is even shown. See SDK initialization failures below.

Not all failures are retryable

Before prompting the user to try again, be aware of two important characteristics of payment failures: A client-side failure doesn’t always mean the payment failed. A failure reported by the SDK can be local to the browser or device — for example, a network drop after the payment was submitted. In that case the payment may have completed successfully even though the client saw an error. Your server — through payment webhooks or the Payments API — is the source of truth for the payment’s status. Verify the status server-side before asking the user to pay again, otherwise you risk charging them twice. Decline reasons are intentionally generic. When a payment is declined, the SDK reports that it failed, but not the specific reason. This is deliberate to avoid leaking details. The detailed reason is available to you server-side via webhooks, the Payments API, and the Primer Dashboard. In practice: show a generic failure message, offer the user a way to retry or choose another payment method, and treat your server-side payment status — not the client-side error — as the definitive outcome.

Deciding whether to retry (Web)

The failure payload tells you which case you’re dealing with. The error on the primer:payment-failure event carries a code identifying the failure, and an origin that says who should act on it: In short: on a network failure, ask your backend whether a payment was created — the SDK couldn’t tell. On any other failure, use payment.id (when present) to cross-check the status and cause with your backend before deciding whether to allow a retry.
The failure payload’s payment carries only id, orderId, and summary payment method data — not the payment’s status. Look the status up by id through your backend, the Payments API, or the Dashboard.
On SDK versions without origin (before 1.6.2 at the top level; 1.6.2 to 1.9.2 expose it nested at error.data.error.origin), you can’t detect a transport-level failure from the error itself. Use the presence of payment data instead: a failure without payment may be network-caused — a payment may still have been created, so verify with your backend before retrying. A failure with payment identifies the payment to cross-check by id.

Default error behavior

The <primer-error-message-container> specifically handles payment failures that occur after form submission, not card validation errors. Card validation is handled by the input components themselves and prevents form submission until valid.

Built-in error display

The <primer-error-message-container> provides a convenient way to display payment failures without writing custom code:
For optimal user experience, place the error container:
  1. Prominently where it will be visible after a payment attempt
  2. Near the action where users will naturally look for feedback after submitting payment
  3. In context within the same visual area as the payment method it relates to

Error properties

Every error from the SDK includes diagnostic information to help identify and resolve issues.

Custom error handling

You can implement your own payment failure handling using the SDK callbacks and events. Choose one of the following approaches:
This approach gives you complete control over payment failure presentation but requires you to implement the error handling logic yourself.

SDK initialization failures

Before checkout can process payments, the SDK needs to initialize — validate the client token, load your Checkout Builder configuration, etc. Treat a failure here separately from the validation and payment errors above: no form has been shown yet, so there’s nothing to retry other than re-initializing the SDK.
This section covers Web. See Default error behavior above for Android and iOS.
When <primer-checkout> fails to initialize, <primer-main> renders its checkout-error slot instead of the payment methods. Provide your own content there to replace the default error screen:
For error details — to log the failure or branch on its cause — listen for primer:state-change and read primerJsError:
If you bypass <primer-main> with a custom slot="main" on <primer-checkout>, handle primerJsError the same way.

Choosing the right approach

Best practices

  1. Always handle errors - Never leave users without feedback after a failed payment
  2. Be specific where you can - Validation errors can name the exact problem; payment declines are intentionally generic, so pair them with actionable next steps instead
  3. Provide next steps - Guide users on how to resolve the issue (try another card, check details, etc.)
  4. Verify before retrying - Confirm the payment status server-side before treating a client-side failure as final; the payment may have succeeded. On Web, branch on the error’s code and origin
  5. Clear on retry - Hide error messages when users attempt a new payment
  6. Log for debugging - Capture error details including diagnosticsId for troubleshooting

See also

Error message container

Web component SDK reference

Android SDK reference

Android SDK API documentation

Events guide

Complete reference for all checkout events

Log Errors Guide

Capture and log payment errors for debugging