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

# Log errors for debugging

> Capture and log payment errors for debugging and support.

Log payment errors with diagnostic information to help debug issues and work with Primer support.

## Recipe

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    checkout.addEventListener('primer:ready', (event) => {
      const primer = event.detail;

      primer.onPaymentFailure = ({ error }) => {
        console.error('Payment failed:', {
          code: error.code,
          message: error.message,
          diagnosticsId: error.diagnosticsId, // Share with Primer support
        });
      };
    });
    ```
  </Tab>

  <Tab title="Android">
    ```kotlin theme={"dark"}
    PrimerCheckoutSheet(
        checkout = checkout,
        onEvent = { event ->
            when (event) {
                is PrimerCheckoutEvent.Failure -> {
                    val error = event.error
                    Log.e("Checkout", "Payment failed: ${error.description}")
                    Log.e("Checkout", "Error code: ${error.errorCode}")
                    Log.e("Checkout", "Diagnostics ID: ${error.diagnosticsId}")
                    Log.e("Checkout", "Recovery: ${error.recoverySuggestion}")
                }
            }
        },
    )
    ```
  </Tab>

  <Tab title="iOS">
    ```swift theme={"dark"}
    PrimerCheckout(
      clientToken: clientToken,
      onCompletion: { state in
        if case .failure(let error) = state {
          print("[Primer] Error ID: \(error.errorId)")
          print("[Primer] Description: \(error.errorDescription ?? "N/A")")
          print("[Primer] Diagnostics: \(error.diagnosticsId ?? "N/A")")
          print("[Primer] Recovery: \(error.recoverySuggestion ?? "N/A")")
        }
      }
    )
    ```
  </Tab>
</Tabs>

## How it works

<Tabs>
  <Tab title="Web">
    1. Listen for the `primer:ready` event to access the Primer SDK instance
    2. Set the `onPaymentFailure` callback
    3. Log the error details including `diagnosticsId` which helps Primer support investigate issues
  </Tab>

  <Tab title="Android">
    1. Use the `onEvent` callback on `PrimerCheckoutSheet` or `PrimerCheckoutHost`
    2. Handle `PrimerCheckoutEvent.Failure` to access the `PrimerError`
    3. Log the error details including `diagnosticsId` which helps Primer support investigate issues

    Every `PrimerError` includes these properties:

    | Property             | Type      | Description                            |
    | -------------------- | --------- | -------------------------------------- |
    | `errorId`            | `String`  | Unique error identifier                |
    | `description`        | `String`  | Human-readable error message           |
    | `errorCode`          | `String?` | Error code (e.g., `"card_declined"`)   |
    | `diagnosticsId`      | `String`  | Reference ID for Primer support        |
    | `recoverySuggestion` | `String?` | Suggested recovery action for the user |
  </Tab>

  <Tab title="iOS">
    1. Use the `onCompletion` callback or observe `checkoutScope.state` for `.failure` cases
    2. Access `PrimerError` properties for debugging details
    3. Log the `diagnosticsId` which helps Primer support investigate issues

    Every `PrimerError` includes these properties:

    | Property             | Type      | Description                            |
    | -------------------- | --------- | -------------------------------------- |
    | `errorId`            | `String`  | Unique error identifier                |
    | `errorDescription`   | `String?` | Human-readable error message           |
    | `diagnosticsId`      | `String?` | Reference ID for Primer support        |
    | `recoverySuggestion` | `String?` | Suggested recovery action for the user |
  </Tab>
</Tabs>

<Tip>
  The `diagnosticsId` is a unique identifier for the error. When contacting Primer support, always include this ID to help them quickly locate and diagnose the issue.
</Tip>

## Variations

### Send errors to logging service

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    primer.onPaymentFailure = ({ error, paymentMethodType }) => {
      // Send to your logging service (Sentry, LogRocket, etc.)
      Sentry.captureException(new Error(error.message), {
        extra: {
          code: error.code,
          diagnosticsId: error.diagnosticsId,
          paymentMethod: paymentMethodType,
        },
      });
    };
    ```
  </Tab>

  <Tab title="Android">
    ```kotlin theme={"dark"}
    is PrimerCheckoutEvent.Failure -> {
        val error = event.error
        Sentry.captureException(Exception(error.description)) { scope ->
            scope.setExtra("error_code", error.errorCode.orEmpty())
            scope.setExtra("diagnostics_id", error.diagnosticsId)
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    ```swift theme={"dark"}
    if case .failure(let error) = state {
      ErrorLogger.log(
        errorId: error.errorId,
        description: error.errorDescription ?? "Unknown error",
        diagnosticsId: error.diagnosticsId ?? "N/A"
      )
    }
    ```
  </Tab>
</Tabs>

### Custom error display

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    const primerEvents = [
      'primer:ready',
      'primer:state-change',
      'primer:methods-update',
      'primer:payment-start',
      'primer:payment-success',
      'primer:payment-failure',
      'primer:card-success',
      'primer:card-error',
      'primer:bin-data-available',
      'primer:vault-methods-update',
    ];

    primerEvents.forEach((eventName) => {
      document.addEventListener(eventName, (event) => {
        console.log(`[${eventName}]`, event.detail);
      });
    });
    ```
  </Tab>

  <Tab title="Android">
    Override the error slot in `PrimerCheckoutSheet` to display a custom error screen:

    ```kotlin theme={"dark"}
    PrimerCheckoutSheet(
        checkout = checkout,
        error = { error ->
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                Icon(
                    imageVector = Icons.Default.Warning,
                    contentDescription = null,
                    tint = MaterialTheme.colorScheme.error,
                    modifier = Modifier.size(48.dp),
                )
                Spacer(Modifier.height(16.dp))
                Text(
                    text = "Something went wrong",
                    style = MaterialTheme.typography.titleLarge,
                )
                Spacer(Modifier.height(8.dp))
                Text(
                    text = error.description,
                    style = MaterialTheme.typography.bodyMedium,
                    textAlign = TextAlign.Center,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
                if (error.recoverySuggestion != null) {
                    Spacer(Modifier.height(4.dp))
                    Text(
                        text = error.recoverySuggestion!!,
                        style = MaterialTheme.typography.bodySmall,
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                        textAlign = TextAlign.Center,
                    )
                }
                Spacer(Modifier.height(24.dp))
                Button(
                    onClick = { checkout.refresh() },
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    Text("Try again")
                }
                Spacer(Modifier.height(8.dp))
                OutlinedButton(
                    onClick = { checkout.dismiss() },
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    Text("Use a different method")
                }
            }
        },
        onEvent = { event ->
            when (event) {
                is PrimerCheckoutEvent.Failure -> {
                    Log.e("Checkout", "Diagnostics: ${event.error.diagnosticsId}")
                }
            }
        },
        onDismiss = { },
    )
    ```
  </Tab>

  <Tab title="iOS">
    Observe state changes and display errors inline:

    ```swift theme={"dark"}
    PrimerCheckout(
      clientToken: clientToken,
      scope: { checkoutScope in
        Task {
          for await state in checkoutScope.state {
            if case .failure(let error) = state {
              print("[Primer] Error: \(error.errorDescription ?? "N/A")")
              print("[Primer] Diagnostics: \(error.diagnosticsId ?? "N/A")")
            }
          }
        }
      }
    )
    ```
  </Tab>
</Tabs>

### Inline error handling

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    checkout.addEventListener('primer:methods-update', (event) => {
      const methods = event.detail.toArray();
      console.table(methods.map((m) => ({ type: m.type, id: m.id })));
    });
    ```
  </Tab>

  <Tab title="Android">
    For `PrimerCheckoutHost`, handle errors through the `onEvent` callback and display your own UI:

    ```kotlin theme={"dark"}
    @Composable
    fun InlineCheckoutWithErrors(clientToken: String) {
        val checkout = rememberPrimerCheckoutController(clientToken)
        val state by checkout.state.collectAsStateWithLifecycle()
        var paymentError by remember { mutableStateOf<PrimerError?>(null) }

        when (state) {
            is PrimerCheckoutState.Loading -> {
                CircularProgressIndicator()
            }
            is PrimerCheckoutState.Ready -> {
                PrimerCheckoutHost(
                    checkout = checkout,
                    onEvent = { event ->
                        when (event) {
                            is PrimerCheckoutEvent.Failure -> {
                                paymentError = event.error
                            }
                            is PrimerCheckoutEvent.Success -> {
                                paymentError = null
                            }
                        }
                    },
                ) {
                    val cardFormController = rememberCardFormController(checkout)

                    Column(Modifier.padding(16.dp)) {
                        PrimerCardForm(controller = cardFormController)

                        paymentError?.let { error ->
                            Spacer(Modifier.height(16.dp))
                            Card(
                                colors = CardDefaults.cardColors(
                                    containerColor = MaterialTheme.colorScheme.errorContainer,
                                ),
                                modifier = Modifier.fillMaxWidth(),
                            ) {
                                Row(
                                    modifier = Modifier.padding(16.dp),
                                    verticalAlignment = Alignment.CenterVertically,
                                ) {
                                    Icon(
                                        imageVector = Icons.Default.Warning,
                                        contentDescription = null,
                                        tint = MaterialTheme.colorScheme.error,
                                    )
                                    Spacer(Modifier.width(12.dp))
                                    Column(Modifier.weight(1f)) {
                                        Text(
                                            text = error.description,
                                            style = MaterialTheme.typography.bodyMedium,
                                        )
                                        if (error.recoverySuggestion != null) {
                                            Text(
                                                text = error.recoverySuggestion!!,
                                                style = MaterialTheme.typography.bodySmall,
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    Handle errors from `onCompletion` and display card field validation errors:

    ```swift theme={"dark"}
    PrimerCheckout(
      clientToken: clientToken,
      scope: { checkoutScope in
        if let cardScope: PrimerCardFormScope = checkoutScope.getPaymentMethodScope(PrimerCardFormScope.self) {
          Task {
            for await state in cardScope.state {
              for fieldError in state.fieldErrors {
                print("[Primer] Field \(fieldError.fieldType): \(fieldError.message)")
              }
            }
          }
        }
      },
      onCompletion: { state in
        if case .failure(let error) = state {
          print("[Primer] Payment error: \(error.errorDescription ?? "N/A")")
        }
      }
    )
    ```
  </Tab>
</Tabs>

## See also

<CardGroup cols={2}>
  <Card title="Track payment in analytics" icon="chart-line" href="/checkout/primer-checkout/guides-and-recipes/track-payment-analytics">
    Send payment events to your analytics platform
  </Card>

  <Card title="Error handling" icon="triangle-exclamation" href="/checkout/primer-checkout/build-your-ui/error-handling">
    Handle payment failures and display error messages
  </Card>
</CardGroup>
