Skip to main content

Quick diagnosis

SymptomLikely CauseSolution
Compose version conflictCompiler version mismatchAlign kotlinCompilerExtensionVersion
Checkout stays in LoadingInvalid/expired client tokenCheck Logcat for errors, regenerate token
Payment methods emptyDashboard misconfigurationVerify Dashboard settings and client session fields
Recomposition causes re-initController created outside rememberUse rememberPrimerCheckoutController()
State not updating in UINot using lifecycle-aware collectUse collectAsStateWithLifecycle()

Installation issues

Compose version conflict

Cause: Build fails with Compose compiler version mismatch. Solution: Ensure your Compose compiler version is compatible:
android {
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.8"
    }
}

Runtime issues

Checkout stays in Loading state

Cause: PrimerCheckoutState.Loading never transitions to Ready. Possible causes:
  • Invalid or expired client token
  • Network connectivity issues
  • Incorrect API key in client session creation
Solution: Check Logcat for error messages from the SDK. Ensure your client token is fresh and generated correctly.

Payment methods not showing

Cause: PrimerPaymentMethods shows an empty list. Possible causes:
  • No payment methods configured in Primer Dashboard
  • Client session missing required fields
  • Payment methods not enabled for the currency/country
Solution: Verify your Primer Dashboard configuration and ensure your client session includes the correct currencyCode and countryCode.

3DS challenge not completing

Cause: Payment gets stuck during 3DS authentication. Solution: Ensure your Activity has android:configChanges="orientation|screenSize" to prevent the WebView from being destroyed during configuration changes.

Compose-specific issues

Recomposition causes re-initialization

Cause: Checkout reinitializes on every recomposition. Solution: Use rememberPrimerCheckoutController() which survives recomposition:
// Correct
val checkout = rememberPrimerCheckoutController(clientToken)

// Incorrect - creates new controller on recomposition
val checkout = PrimerCheckoutController(clientToken)

State not updating in UI

Cause: UI doesn’t reflect state changes from the SDK. Solution: Use collectAsStateWithLifecycle():
val state by checkout.state.collectAsStateWithLifecycle()

Validation vs payment errors

Understanding the difference helps with proper error handling:
Error TypeWhen It OccursHow It’s Handled
Validation errorsDuring input (invalid format, missing fields)Handled automatically by input components; prevents form submission
Payment failuresAfter form submission (declined card, network issues)Requires explicit handling with error container or custom code
Don’t confuse these two error types. Validation errors prevent form submission and are shown inline. Payment failures occur after the form is submitted and require explicit handling.

Debugging tips

Log checkout state changes

LaunchedEffect(checkout) {
    checkout.state.collect { state ->
        Log.d("PrimerCheckout", "State: $state")
    }
}

Log all checkout events

PrimerCheckoutSheet(
    checkout = checkout,
    onEvent = { event ->
        Log.d("PrimerCheckout", "Event: $event")
        when (event) {
            is PrimerCheckoutEvent.Success -> { }
            is PrimerCheckoutEvent.Failure -> {
                Log.e("PrimerCheckout", "diagnosticsId: ${event.error.diagnosticsId}")
            }
        }
    },
)

Getting help

When contacting Primer support, include:
  1. The diagnosticsId from any error callbacks
  2. Your Android API level, Compose version, and SDK version
  3. Steps to reproduce the issue
onEvent = { event ->
    when (event) {
        is PrimerCheckoutEvent.Failure -> {
            Log.e("Checkout", "diagnosticsId: ${event.error.diagnosticsId}")
        }
    }
}

See also

Events guide

Event handling patterns