{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","admonition"]},"type":"markdown"},"seo":{"title":"Troubleshooting","description":"Transform your commerce with PXP's unified platform—seamless payments, real-time insights, and global growth in one powerful integration.","lang":"en-UK","siteUrl":"https://developer.pxp.io","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"troubleshooting","__idx":0},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"SubHeading","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Learn how to diagnose and fix common issues with Checkout Drop-in."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"quick-diagnostics","__idx":1},"children":["Quick diagnostics"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you're experiencing issues with Drop-in, start with these quick diagnostic checks:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.pxp.checkout.checkoutdropin.CheckoutDropIn\nimport com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\nimport android.util.Log\n\n// Drop-in diagnostic helper\nfun diagnoseDropIn(\n    context: Context,\n    session: SessionConfig?,\n    config: CheckoutDropInConfig\n) {\n    Log.d(\"DropInDiagnostics\", \"=== Checkout Drop-in diagnostics ===\")\n    \n    // Check SDK import\n    Log.d(\"DropInDiagnostics\", \"SDK imported: ${CheckoutDropIn::class.java.name}\")\n    \n    // Check session data\n    Log.d(\"DropInDiagnostics\", \"Session ID: ${if (session?.sessionId != null) \"Present\" else \"Missing\"}\")\n    Log.d(\"DropInDiagnostics\", \"HMAC key: ${if (session?.hmacKey != null) \"Present\" else \"Missing\"}\")\n    Log.d(\"DropInDiagnostics\", \"Allowed funding types: ${session?.allowedFundingTypes}\")\n    \n    // Check environment\n    Log.d(\"DropInDiagnostics\", \"Environment: ${config.environment}\")\n    Log.d(\"DropInDiagnostics\", \"Owner ID: ${config.ownerId}\")\n    Log.d(\"DropInDiagnostics\", \"Owner type: ${config.ownerType}\")\n    \n    // Check device info\n    Log.d(\"DropInDiagnostics\", \"Android version: ${Build.VERSION.SDK_INT}\")\n    Log.d(\"DropInDiagnostics\", \"Device: ${Build.MANUFACTURER} ${Build.MODEL}\")\n    Log.d(\"DropInDiagnostics\", \"Screen density: ${context.resources.displayMetrics.density}\")\n    \n    // Check network connectivity\n    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n    val network = connectivityManager.activeNetwork\n    val capabilities = connectivityManager.getNetworkCapabilities(network)\n    Log.d(\"DropInDiagnostics\", \"Network available: ${capabilities != null}\")\n    \n    Log.d(\"DropInDiagnostics\", \"===================================\")\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"common-issues","__idx":2},"children":["Common issues"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"drop-in-not-rendering","__idx":3},"children":["Drop-in not rendering"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The Drop-in component doesn't appear in your Compose UI."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["No payment methods are visible."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["There are no errors in Logcat."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Solution"},"children":["Solution"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Compose recomposition issue."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Ensure session data is loaded before rendering ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropIn"]},". Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["LaunchedEffect"]}," to fetch session data and conditional rendering."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Session data invalid."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify session data includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hmacKey"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes"]},". Check backend session creation logs."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No payment methods enabled."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Check ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes"]}," in your session. At least one payment method must be enabled in the Unity Portal."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Incorrect configuration."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["environment"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ownerId"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ownerType"]}," are set correctly."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ProGuard/R8 stripping SDK classes."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Add ProGuard rules to keep SDK classes (see ProGuard section below)."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Diagnostic steps:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport android.util.Log\nimport com.pxp.checkout.checkoutdropin.CheckoutDropIn\nimport com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\nimport com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\n\n@Composable\nfun DiagnoseRenderingIssue() {\n    val context = LocalContext.current\n    var session by remember { mutableStateOf<SessionConfig?>(null) }\n    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\n    var diagnostics by remember { mutableStateOf(\"\") }\n    \n    LaunchedEffect(Unit) {\n        diagnostics += \"Step 1: Fetching session...\\n\"\n        \n        try {\n            val response = apiClient.post(\"/api/create-session\") {\n                contentType(ContentType.Application.Json)\n            }\n            \n            if (response.status.value == 200) {\n                val result = response.body<SessionResponse>()\n                if (result.success && result.data != null) {\n                    session = result.data\n                    diagnostics += \"Step 2: Session fetched successfully\\n\"\n                    diagnostics += \"Session ID: ${result.data.sessionId}\\n\"\n                } else {\n                    diagnostics += \"ERROR: Session fetch failed: ${result.error}\\n\"\n                }\n            } else {\n                diagnostics += \"ERROR: HTTP ${response.status.value}\\n\"\n            }\n        } catch (e: Exception) {\n            diagnostics += \"ERROR: ${e.message}\\n\"\n            Log.e(\"DropIn\", \"Session fetch error\", e)\n        }\n        \n        // Step 3: Check session data\n        session?.let { sessionConfig ->\n            diagnostics += \"Step 3: Validating session data...\\n\"\n            \n            if (sessionConfig.sessionId.isNullOrEmpty()) {\n                diagnostics += \"ERROR: Session ID missing\\n\"\n            } else {\n                diagnostics += \"Session ID present\\n\"\n            }\n            \n            if (sessionConfig.allowedFundingTypes == null) {\n                diagnostics += \"ERROR: No allowed funding types\\n\"\n            } else {\n                var paymentMethodCount = 0\n                if (sessionConfig.allowedFundingTypes.cards != null) paymentMethodCount++\n                if (sessionConfig.allowedFundingTypes.wallets?.paypal != null) paymentMethodCount++\n                if (sessionConfig.allowedFundingTypes.wallets?.googlePay != null) paymentMethodCount++\n                \n                if (paymentMethodCount == 0) {\n                    diagnostics += \"ERROR: No payment methods enabled\\n\"\n                } else {\n                    diagnostics += \"$paymentMethodCount payment method(s) available\\n\"\n                }\n            }\n        }\n    }\n    \n    // Initialize Drop-in once session is loaded\n    LaunchedEffect(session) {\n        session?.let { sessionConfig ->\n            val checkoutDropIn = CheckoutDropIn.initialize(\n                context = context,\n                config = CheckoutDropInConfig(\n                    environment = Environment.TEST,\n                    session = sessionConfig,\n                    ownerType = \"MerchantGroup\",\n                    ownerId = \"MERCHANT-1\",\n                    transactionData = DropInTransactionData(\n                        currency = \"GBP\",\n                        amount = 1.00,\n                        entryType = EntryType.Ecom,\n                    intent = DropInTransactionIntentData(\n                        card = IntentType.Authorisation\n                    ),\n                    merchant = \"Demo Store\",\n                    merchantTransactionId = \"test-${System.currentTimeMillis()}\",\n                        merchantTransactionDate = { Instant.now().toString() }\n                    ),\n                    onSuccess = { result ->\n                        Log.d(\"DropIn\", \"Success: ${result.systemTransactionId}\")\n                    },\n                    onError = { error ->\n                        Log.e(\"DropIn\", \"Error: ${error.message}\", error)\n                    }\n                )\n            )\n            checkoutDropInComponent = checkoutDropIn.create()\n        }\n    }\n    \n    Column {\n        Text(diagnostics)\n        checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"session-expired-errors","__idx":4},"children":["Session expired errors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A \"Session expired\" error message is displayed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The drop-in loads but payment fails immediately."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Logcat shows session timeout errors."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Solution"},"children":["Solution"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Session timeout exceeded."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sessions expire based on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionTimeout"]}," value (default = 120 minutes). Create a new session if expired."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Clock skew"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Ensure that the server and device clocks are synchronised."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Session reused"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sessions are single-use. Create a new session for each checkout attempt."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Invalid HMAC signature"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify that the HMAC key matches between session creation and SDK initialisation."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution: Implement session refresh"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport kotlinx.coroutines.launch\nimport com.pxp.checkout.checkoutdropin.CheckoutDropIn\nimport com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\nimport com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\n\n@Composable\nfun CheckoutScreenWithSessionRefresh() {\n    val context = LocalContext.current\n    val coroutineScope = rememberCoroutineScope()\n    var session by remember { mutableStateOf<SessionConfig?>(null) }\n    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\n    var refreshKey by remember { mutableStateOf(0) }\n    \n    // Fetch session\n    LaunchedEffect(refreshKey) {\n        try {\n            session = fetchSessionFromBackend()\n        } catch (e: Exception) {\n            Log.e(\"Checkout\", \"Failed to fetch session\", e)\n            Toast.makeText(\n                context,\n                \"Failed to load checkout. Please try again.\",\n                Toast.LENGTH_LONG\n            ).show()\n        }\n    }\n    \n    // Initialize Drop-in once session is loaded\n    LaunchedEffect(session) {\n        session?.let { sessionConfig ->\n            val checkoutDropIn = CheckoutDropIn.initialize(\n                context = context,\n                config = CheckoutDropInConfig(\n                    environment = Environment.LIVE,\n                    session = sessionConfig,\n                    ownerType = \"MerchantGroup\",\n                    ownerId = \"MERCHANT-1\",\n                    transactionData = DropInTransactionData(\n                        currency = \"GBP\",\n                        amount = 99.99,\n                        entryType = EntryType.Ecom,\n                            intent = DropInTransactionIntentData(\n                                card = IntentType.Authorisation\n                            ),\n                            merchant = \"Demo Store\",\n                            merchantTransactionId = UUID.randomUUID().toString(),\n                        merchantTransactionDate = { Instant.now().toString() }\n                    ),\n                    onSuccess = { result ->\n                        coroutineScope.launch {\n                            verifyPaymentOnBackend(result)\n                        }\n                    },\n                    onError = { error ->\n                        // Handle session expiry\n                        if (error.message?.contains(\"expired\", ignoreCase = true) == true ||\n                            error.message?.contains(\"session\", ignoreCase = true) == true) {\n                            Log.w(\"Checkout\", \"Session expired, refreshing...\")\n                            \n                            Toast.makeText(\n                                context,\n                                \"Session expired. Refreshing checkout...\",\n                                Toast.LENGTH_SHORT\n                            ).show()\n                            \n                            // Trigger session refresh\n                            refreshKey++\n                        } else {\n                            Log.e(\"Checkout\", \"Payment error\", error)\n                            Toast.makeText(\n                                context,\n                                \"Payment failed: ${error.message}\",\n                                Toast.LENGTH_LONG\n                            ).show()\n                        }\n                    }\n                )\n            )\n            checkoutDropInComponent = checkoutDropIn.create()\n        }\n    }\n    \n    // Render Drop-in\n    checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\n}\n\nsuspend fun fetchSessionFromBackend(): SessionConfig {\n    val response = apiClient.post(\"/api/create-session\") {\n        contentType(ContentType.Application.Json)\n    }\n    \n    if (response.status.value != 200) {\n        throw Exception(\"Failed to create session: HTTP ${response.status.value}\")\n    }\n    \n    val result = response.body<SessionResponse>()\n    if (!result.success || result.data == null) {\n        throw Exception(\"Session creation failed: ${result.error}\")\n    }\n    \n    return result.data\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"payment-method-not-appearing","__idx":5},"children":["Payment method not appearing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["An expected payment method isn't shown."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Only the card payment method is visible."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Google Pay doesn't appear even though it's enabled."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Payment method"},"children":["Payment method"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Common causes"},"children":["Common causes"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Solutions"},"children":["Solutions"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cards"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cards aren't enabled in the Unity Portal or are missing from the session configuration."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Enable the Card service in the Unity Portal and verify that the session includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes.cards"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["PayPal"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["PayPal onboarding wasn't completed or is missing from session configuration."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Complete PayPal onboarding in Unity Portal and verify that the session includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes.wallets.paypal"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Google Pay"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Google Play Services not available, no cards in Google Wallet, or unsupported device."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify Google Play Services is installed, add test cards to Google Wallet, and test on a physical device or emulator with Play Services."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Diagnostic steps:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.google.android.gms.common.ConnectionResult\nimport com.google.android.gms.common.GoogleApiAvailability\n\nfun diagnosePaymentMethodVisibility(\n    context: Context,\n    session: SessionConfig?\n) {\n    Log.d(\"PaymentMethods\", \"=== Payment method diagnostics ===\")\n    \n    val fundingTypes = session?.allowedFundingTypes\n    Log.d(\"PaymentMethods\", \"Session funding types: $fundingTypes\")\n    \n    // Check cards\n    if (fundingTypes?.cards != null) {\n        Log.d(\"PaymentMethods\", \"Cards enabled: ${fundingTypes.cards}\")\n    } else {\n        Log.w(\"PaymentMethods\", \"Cards not enabled in session\")\n    }\n    \n    // Check PayPal\n    if (fundingTypes?.wallets?.paypal != null) {\n        Log.d(\"PaymentMethods\", \"PayPal enabled: ${fundingTypes.wallets.paypal}\")\n    } else {\n        Log.w(\"PaymentMethods\", \"PayPal not enabled in session\")\n    }\n    \n    // Check Google Pay\n    if (fundingTypes?.wallets?.googlePay != null) {\n        Log.d(\"PaymentMethods\", \"Google Pay configuration present\")\n        \n        // Check Google Play Services availability\n        val apiAvailability = GoogleApiAvailability.getInstance()\n        val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)\n        \n        if (resultCode == ConnectionResult.SUCCESS) {\n            Log.d(\"PaymentMethods\", \"Google Play Services available\")\n        } else {\n            Log.w(\"PaymentMethods\", \"Google Play Services not available: $resultCode\")\n        }\n    } else {\n        Log.w(\"PaymentMethods\", \"Google Pay not configured in session\")\n    }\n    \n    // Check device info\n    Log.d(\"PaymentMethods\", \"Android API level: ${Build.VERSION.SDK_INT}\")\n    Log.d(\"PaymentMethods\", \"Device: ${Build.MANUFACTURER} ${Build.MODEL}\")\n    \n    Log.d(\"PaymentMethods\", \"===================================\")\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"google-pay-not-working","__idx":6},"children":["Google Pay not working"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Google Pay button doesn't appear."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Google Pay sheet doesn't open when tapped."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Payment fails with Google Pay errors."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Solution"},"children":["Solution"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Google Play Services not installed."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Install Google Play Services on the device or use an emulator with Play Services. Complete Google Pay onboarding in Unity Portal and verify that the session includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes.wallets.googlePay"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No cards in Google Wallet."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Add test cards to Google Wallet manually."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Minimum API level not met."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Google Pay requires Android 7.0 (API level 24) or higher."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Merchant ID missing or invalid."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Add a valid Google Pay merchant ID in production (optional for testing)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Device not supported."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Test on a physical device or emulator with Google Play."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.google.android.gms.wallet.PaymentsClient\nimport com.google.android.gms.wallet.Wallet\nimport com.google.android.gms.wallet.IsReadyToPayRequest\nimport org.json.JSONObject\nimport org.json.JSONArray\n\nfun checkGooglePayAvailability(context: Context) {\n    val paymentsClient: PaymentsClient = Wallet.getPaymentsClient(\n        context,\n        Wallet.WalletOptions.Builder()\n            .setEnvironment(WalletConstants.ENVIRONMENT_TEST)\n            .build()\n    )\n    \n    val request = IsReadyToPayRequest.fromJson(\n        JSONObject().apply {\n            put(\"apiVersion\", 2)\n            put(\"apiVersionMinor\", 0)\n            put(\"allowedPaymentMethods\", JSONArray().apply {\n                put(JSONObject().apply {\n                    put(\"type\", \"CARD\")\n                    put(\"parameters\", JSONObject().apply {\n                        put(\"allowedAuthMethods\", JSONArray().apply {\n                            put(\"PAN_ONLY\")\n                            put(\"CRYPTOGRAM_3DS\")\n                        })\n                        put(\"allowedCardNetworks\", JSONArray().apply {\n                            put(\"VISA\")\n                            put(\"MASTERCARD\")\n                        })\n                    })\n                })\n            })\n        }.toString()\n    )\n    \n    paymentsClient.isReadyToPay(request).addOnCompleteListener { task ->\n        if (task.isSuccessful) {\n            Log.d(\"GooglePay\", \"Google Pay is available: ${task.result}\")\n        } else {\n            Log.w(\"GooglePay\", \"Google Pay check failed\", task.exception)\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"backend-verification-failing","__idx":7},"children":["Backend verification failing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Frontend ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," fires but backend verification fails."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Orders aren't being fulfilled."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["\"Payment verification failed\" errors."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Cause"},"children":["Cause"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Solution"},"children":["Solution"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Webhook not configured."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Set up a webhook URL in the Unity Portal and implement a webhook handler on your backend."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Webhook authentication failing."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify your webhook signature/authentication. Check your HMAC implementation."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Race condition (GET before webhook)."]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Implement a fallback to the ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["Get transaction details API"]}," if the webhook hasn't arrived yet."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Transaction ID mismatch"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Verify that the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["systemTransactionId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionId"]}," match database records."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution: Robust backend verification"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Backend webhook handler (Node.js/Express example)\napp.post('/webhooks/pxp', async (req, res) => {\n  try {\n    const events = req.body;\n    \n    // Verify webhook authenticity using HMAC\n    if (!verifyWebhookSignature(req)) {\n      console.error('Invalid webhook signature');\n      return res.status(401).json({ error: 'Unauthorised' });\n    }\n    \n    for (const event of events) {\n      if (event.eventCategory === 'Transaction') {\n        const txn = event.eventData;\n        \n        console.log('Processing transaction:', txn.systemTransactionId);\n        \n        // Idempotency check\n        const existing = await db.transactions.findOne({\n          systemTransactionId: txn.systemTransactionId\n        });\n        \n        if (existing) {\n          console.log('Transaction already processed, skipping');\n          continue;\n        }\n        \n        // Verify transaction state\n        if (txn.state === 'Authorised' || txn.state === 'Captured') {\n          // Find order by merchant transaction ID\n          const order = await db.orders.findOne({\n            transactionId: txn.merchantTransactionId\n          });\n          \n          if (!order) {\n            console.error('Order not found:', txn.merchantTransactionId);\n            continue;\n          }\n          \n          // Verify amount matches\n          const transactionAmount = txn.amounts?.transactionValue || txn.amount;\n          if (Math.abs(transactionAmount - order.amount) > 0.01) {\n            console.error('Amount mismatch:', {\n              expected: order.amount,\n              actual: transactionAmount\n            });\n            continue;\n          }\n          \n          // Mark transaction as processed\n          await db.transactions.create({\n            systemTransactionId: txn.systemTransactionId,\n            merchantTransactionId: txn.merchantTransactionId,\n            amount: transactionAmount,\n            state: txn.state,\n            processedAt: new Date()\n          });\n          \n          // Fulfill order\n          await fulfillOrder(order.id, txn.systemTransactionId);\n          \n          console.log('Order fulfilled:', order.id);\n        }\n      }\n    }\n    \n    // Always return success\n    res.json({ state: 'Success' });\n  } catch (error) {\n    console.error('Webhook processing error:', error);\n    res.status(500).json({ error: 'Internal server error' });\n  }\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"android-specific-issues","__idx":8},"children":["Android-specific issues"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"proguardr8-stripping-sdk-classes","__idx":9},"children":["ProGuard/R8 stripping SDK classes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Drop-in works in debug builds but crashes in release builds."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ClassNotFoundException"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["NoSuchMethodException"]}," in production."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution: Add ProGuard rules"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create or update ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["proguard-rules.pro"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"proguard","header":{"controls":{"copy":{}}},"source":"# Keep PXP SDK classes\n-keep class com.pxp.checkout.** { *; }\n-keepclassmembers class com.pxp.checkout.** { *; }\n\n# Keep data classes used for serialization\n-keep @kotlinx.serialization.Serializable class ** {\n    *;\n}\n\n# Keep Kotlin metadata\n-keepattributes *Annotation*\n-keepattributes Signature\n-keepattributes InnerClasses\n-keepattributes EnclosingMethod\n\n# Keep Compose\n-keep class androidx.compose.** { *; }\n-keepclassmembers class androidx.compose.** { *; }\n","lang":"proguard"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"memory-leaks","__idx":10},"children":["Memory leaks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["App performance degrades over time."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Android Studio Profiler shows growing memory usage."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Out of memory crashes after multiple checkout attempts."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution: Proper lifecycle management"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport com.pxp.checkout.checkoutdropin.CheckoutDropIn\nimport com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\nimport com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\n\n@Composable\nfun CheckoutScreen() {\n    val context = LocalContext.current\n    val lifecycleOwner = LocalLifecycleOwner.current\n    var sessionData by remember { mutableStateOf<SessionConfig?>(null) }\n    var isActive by remember { mutableStateOf(true) }\n    \n    // Monitor lifecycle\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            when (event) {\n                Lifecycle.Event.ON_PAUSE -> {\n                    isActive = false\n                    Log.d(\"Checkout\", \"Paused\")\n                }\n                Lifecycle.Event.ON_RESUME -> {\n                    isActive = true\n                    Log.d(\"Checkout\", \"Resumed\")\n                }\n                Lifecycle.Event.ON_DESTROY -> {\n                    isActive = false\n                    sessionData = null\n                    Log.d(\"Checkout\", \"Destroyed\")\n                }\n                else -> {}\n            }\n        }\n        \n        lifecycleOwner.lifecycle.addObserver(observer)\n        \n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n            // Clean up resources\n            sessionData = null\n        }\n    }\n    \n    // Only render if active\n    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\n    \n    LaunchedEffect(isActive, sessionData) {\n        if (isActive && sessionData != null) {\n            val checkoutDropIn = CheckoutDropIn.initialize(\n                context = context,\n                config = CheckoutDropInConfig(\n                    // ... config\n                )\n            )\n            checkoutDropInComponent = checkoutDropIn.create()\n        } else {\n            checkoutDropInComponent = null\n        }\n    }\n    \n    // Render component if available\n    checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"keyboard-covering-input-fields","__idx":11},"children":["Keyboard covering input fields"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Symptoms:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Keyboard covers the submit button."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["User can't see what they're typing."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Layout doesn't adjust when keyboard appears."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Solution: Proper window insets handling"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport com.pxp.checkout.checkoutdropin.CheckoutDropIn\nimport com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\nimport com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\n\n@Composable\nfun CheckoutScreen() {\n    val context = LocalContext.current\n    val scrollState = rememberScrollState()\n    \n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding() // Adjust for keyboard\n    ) { paddingValues ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(paddingValues)\n                .verticalScroll(scrollState)\n                .padding(16.dp)\n        ) {\n            // Your checkout content\n            var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\n            \n            LaunchedEffect(Unit) {\n                val checkoutDropIn = CheckoutDropIn.initialize(\n                    context = context,\n                    config = CheckoutDropInConfig(\n                        // ... config\n                    )\n                )\n                checkoutDropInComponent = checkoutDropIn.create()\n            }\n            \n            checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AndroidManifest.xml"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"xml","header":{"controls":{"copy":{}}},"source":"<activity\n    android:name=\".CheckoutActivity\"\n    android:windowSoftInputMode=\"adjustResize\">\n</activity>\n","lang":"xml"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-codes-reference","__idx":12},"children":["Error codes reference"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a complete list of error codes and their meanings, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/drop-in/android/error-handling"},"children":["Error handling"]}," guide."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"getting-additional-help","__idx":13},"children":["Getting additional help"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you're still experiencing issues, try these troubleshooting steps."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"enable-debug-logging","__idx":14},"children":["Enable debug logging"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add detailed logging using Logcat:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"CheckoutDropInConfig(\n    // ... other config\n    analyticsEvent = { event ->\n        if (BuildConfig.DEBUG) {\n            Log.d(\"DropInAnalytics\", \"\"\"\n                Event: ${event.eventName}\n                Session ID: ${event.sessionId}\n                Timestamp: ${event.timestamp}\n                Data: ${event.toMap()}\n            \"\"\".trimIndent())\n        }\n    }\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"collect-diagnostic-information","__idx":15},"children":["Collect diagnostic information"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When contacting support, include:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import android.content.Context\nimport android.os.Build\nimport org.json.JSONObject\n\nfun collectDiagnosticInfo(context: Context, session: SessionConfig?): String {\n    val info = JSONObject().apply {\n        // Device information\n        put(\"manufacturer\", Build.MANUFACTURER)\n        put(\"model\", Build.MODEL)\n        put(\"androidVersion\", Build.VERSION.SDK_INT)\n        put(\"androidRelease\", Build.VERSION.RELEASE)\n        \n        // Screen information\n        val metrics = context.resources.displayMetrics\n        put(\"screenWidth\", metrics.widthPixels)\n        put(\"screenHeight\", metrics.heightPixels)\n        put(\"screenDensity\", metrics.density)\n        \n        // App information\n        val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)\n        put(\"appVersion\", packageInfo.versionName)\n        put(\"appVersionCode\", packageInfo.versionCode)\n        \n        // SDK information (update these placeholders with your actual values)\n        put(\"sdkVersion\", \"1.0.0\") // TODO: Replace with actual SDK version from your build\n        put(\"environment\", \"test\") // TODO: Replace with actual environment from your config\n        \n        // Session info (sanitised)\n        put(\"sessionIdPresent\", session?.sessionId != null)\n        put(\"allowedFundingTypes\", session?.allowedFundingTypes.toString())\n        \n        // Timestamp\n        put(\"timestamp\", Instant.now().toString())\n    }\n    \n    val diagnostics = info.toString(2)\n    Log.d(\"Diagnostics\", diagnostics)\n    \n    // Copy to clipboard\n    val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n    val clip = ClipData.newPlainText(\"Diagnostic Info\", diagnostics)\n    clipboard.setPrimaryClip(clip)\n    \n    return diagnostics\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When contacting support, always include your merchant ID, environment (test or production), Android version, device model, and any relevant error messages or Logcat logs."]}]}]},"headings":[{"value":"Troubleshooting","id":"troubleshooting","depth":1},{"value":"Quick diagnostics","id":"quick-diagnostics","depth":2},{"value":"Common issues","id":"common-issues","depth":2},{"value":"Drop-in not rendering","id":"drop-in-not-rendering","depth":3},{"value":"Session expired errors","id":"session-expired-errors","depth":3},{"value":"Payment method not appearing","id":"payment-method-not-appearing","depth":3},{"value":"Google Pay not working","id":"google-pay-not-working","depth":3},{"value":"Backend verification failing","id":"backend-verification-failing","depth":3},{"value":"Android-specific issues","id":"android-specific-issues","depth":2},{"value":"ProGuard/R8 stripping SDK classes","id":"proguardr8-stripping-sdk-classes","depth":3},{"value":"Memory leaks","id":"memory-leaks","depth":3},{"value":"Keyboard covering input fields","id":"keyboard-covering-input-fields","depth":3},{"value":"Error codes reference","id":"error-codes-reference","depth":2},{"value":"Getting additional help","id":"getting-additional-help","depth":2},{"value":"Enable debug logging","id":"enable-debug-logging","depth":3},{"value":"Collect diagnostic information","id":"collect-diagnostic-information","depth":3}],"frontmatter":{"seo":{"title":"Troubleshooting"}},"lastModified":"2026-05-22T10:12:33.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/drop-in/android/troubleshooting","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}