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. Theerror 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.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
- Web
- Android
- iOS
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
- Web
- Android
- iOS
The For optimal user experience, place the error container:
<primer-error-message-container> provides a convenient way to display payment failures without writing custom code:- Prominently where it will be visible after a payment attempt
- Near the action where users will naturally look for feedback after submitting payment
- 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.- Web
- Android
- iOS
Custom error handling
- Web
- Android
- iOS
You can implement your own payment failure handling using the SDK callbacks and events. Choose one of the following approaches:Before showing a retry option in this handler, check the error’s This approach gives you complete control over payment failure presentation but requires you to implement the error handling logic yourself.
- Payment events (recommended)
- State change event
code and origin — see Deciding whether to retry.The
onPaymentSuccess and onPaymentFailure callbacks on the primer:ready instance still work, but are deprecated in favor of the primer:payment-success and primer:payment-failure events.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.
<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:
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
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