# Error handling Handle errors that occur during onramp sessions. For general error handling, see the [Stripe error handling guide](https://docs.stripe.com/error-handling.md). For error codes specific to the crypto onramp, see [Crypto onramp error codes](https://docs.stripe.com/crypto/onramp/embedded-components-error-codes.md). # React Native ### Checkout errors When the [checkout API](https://docs.stripe.com/api/crypto/onramp_sessions/checkout.md) returns 200 or 202 but the purchase isn’t done, the response body includes the [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions.md) object with `transaction_details.last_error` set. For SDK integrations, `performCheckout` handles payment next actions such as 3DS internally, so the table below only covers `last_error` values that require explicit action from your integration: | **last\_error** | **Description** | **How to handle** | | ------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | `missing_kyc` | KYC verification is required. | Have the user complete KYC in the SDK (for example, `attachKycInfo`). Then call checkout again. | | `missing_document_verification` | Identity document verification is required. | Have the user complete verification in the SDK (for example, `verifyIdentity`). Then call checkout again. | | `charged_with_expired_quote` | Quote expired. | Call [Refresh a Quote](https://docs.stripe.com/api/crypto/onramp_sessions/quote.md) API, then call checkout again. | | `transaction_limit_reached` | User’s limit exceeded. | Display an error message. | | `location_not_supported` | User’s location isn’t supported. | Show that the service isn’t available in their region. | | `transaction_failed` | Generic failure. | Display a generic error message. | | `missing_consumer_wallet` | The wallet address doesn’t exist for the current user. | Have the user register the wallet, then call checkout again. | The following pseudocode shows how to handle these cases in a retry loop: ```jsx import { useOnramp } from '@stripe/stripe-react-native'; function CheckoutWithRetryComponent() { const { attachKycInfo, verifyIdentity, registerWalletAddress, performCheckout } = useOnramp(); const handleCheckout = async () => { // Step 1: Create a new session on your backend before starting checkout. let sessionId = await createSessionOnBackend(); for (let attempt = 0; attempt < 5; attempt++) { const result = await performCheckout(sessionId, async (onrampSessionId) => { // Call your back end, which calls POST /v1/crypto/onramp_sessions/{sessionId}/checkout // and return the client_secret from the response. return await callBackendCheckout(onrampSessionId); }); if (!result.error) { // Purchase complete. return; } if (result.error.code === 'Canceled') { // User canceled. Stop retrying. return; } // Step 2: Inspect last_error from the session to decide the next action. const session = await fetchSessionFromBackend(sessionId); const lastError = session.transaction_details?.last_error; if (lastError === 'missing_kyc') { // Prompt user to provide KYC info, then retry checkout on the same session. await attachKycInfo({ firstName, lastName, idNumber, dateOfBirth, address }); } else if (lastError === 'missing_document_verification') { // Prompt user to complete identity verification, then retry on the same session. await verifyIdentity(); } else if (lastError === 'missing_consumer_wallet') { // Prompt user to register a wallet, then retry on the same session. await registerWalletAddress(walletAddress, network); } else if (lastError === 'charged_with_expired_quote') { // Quote expired. Refresh the quote on your backend, then retry checkout on the same session. await refreshQuoteOnBackend(sessionId); } else { // Terminal errors: transaction_limit_reached, location_not_supported, transaction_failed. // Do not retry. Show an appropriate error message. showError(lastError); return; } } }; return