Debug common issues with PayPal payouts in your Android app.
This guide helps you diagnose and resolve common issues when implementing PayPal payouts in your Android application.
Symptoms:
- Exception thrown during
PxpCheckout.builder().build(). - App crashes on initialisation.
Causes:
- Missing or invalid session data.
- Invalid environment configuration.
- Missing required transaction data.
Solutions:
try {
val pxpCheckout = PxpCheckout.builder()
.withConfig(
PxpSdkConfig(
environment = Environment.TEST, // Ensure valid environment
session = sessionData, // Verify session data is not null
ownerId = "Unity",
ownerType = "MerchantGroup",
clientId = "your-client-id",
transactionData = TransactionData(
amount = 100.0, // Must be > 0
currency = "USD", // Must be valid ISO code
merchant = "your-merchant-id",
entryType = EntryType.Ecom,
intent = TransactionIntentData(paypal = IntentType.Payout),
merchantTransactionId = UUID.randomUUID().toString(),
merchantTransactionDate = { System.currentTimeMillis() }
),
paypalConfig = PaypalConfig(
payout = PayoutConfig(
proceedPayoutWithSdk = true
)
)
)
)
.withContext(context) // Ensure context is valid
.build()
} catch (e: Exception) {
Log.e("Payout", "SDK initialisation failed", e)
showError("Failed to initialise payout SDK: ${e.message}")
}Symptoms:
- Exception when calling
createComponent(). - Component not rendering.
Causes:
- Incorrect component type.
- Invalid configuration.
- SDK not properly initialised.
Solutions:
// Verify component type
val component = try {
pxpCheckout.createComponent(
type = ComponentType.PAYOUT_AMOUNT, // Ensure correct enum value
config = PayoutAmountComponentConfig(
label = "Withdrawal Amount"
)
)
} catch (e: Exception) {
Log.e("Payout", "Component creation failed", e)
null
}
if (component == null) {
showError("Failed to create payout component")
return
}Cause: PayPal wallet email or payer ID not provided or invalid for withdrawal flow.
Solution:
// Ensure both email and payerId are provided
paypalConfig = PaypalConfig(
payout = PayoutConfig(
proceedPayoutWithSdk = true,
paypalWallet = PayPalPayOutWalletConfig(
email = "customer@example.com", // Required
payerId = "PAYER_ID_XXX" // Required
)
)
)Causes:
- Amount is invalid (PO02).
- Currency is invalid (PO03).
- Transaction date is invalid (PO07).
Solution:
// Validate amount before initialisation
fun validateAmount(amount: Double?): Boolean {
return when {
amount == null -> {
showError("Amount is required")
false
}
amount <= 0 -> {
showError("Amount must be greater than zero")
false
}
amount.isNaN() || amount.isInfinite() -> {
showError("Amount must be a valid number")
false
}
else -> true
}
}
if (!validateAmount(payoutAmount)) {
return // Don't proceed with invalid amount
}
transactionData = TransactionData(
amount = payoutAmount, // Validated amount
// ... other properties
)Symptoms:
- Payout fails with network-related error.
- Long delay before error callback.
Solutions:
- Check internet connectivity:
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
// Check before payout
if (!isNetworkAvailable(context)) {
showError("No internet connection. Please check your network and try again.")
return
}- Implement retry logic:
var retryCount = 0
val MAX_RETRIES = 3
onError = { error ->
val isNetworkError = error.errorCode == "NETWORK_ERROR" ||
error.errorReason?.contains("network", ignoreCase = true) == true
if (isNetworkError && retryCount < MAX_RETRIES) {
retryCount++
Handler(Looper.getMainLooper()).postDelayed({
showMessage("Retrying... (Attempt $retryCount of $MAX_RETRIES)")
}, 2000L * retryCount)
} else {
retryCount = 0
showError("Network error. Please try again later.")
}
}Causes:
- Component not properly rendered in Compose.
- Modifier constraints hiding component.
- Component not created successfully.
Solutions:
- Verify component creation:
val component = remember {
pxpCheckout.createComponent(type, config)
}
// Check if component is not null
if (component != null) {
pxpCheckout.buildComponentView(
component = component,
modifier = Modifier.fillMaxWidth()
)
} else {
Text("Failed to load component")
}- Check Compose hierarchy:
@Composable
fun PayoutScreen() {
Surface(modifier = Modifier.fillMaxSize()) { // Ensure parent has size
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
pxpCheckout.buildComponentView(
component = component,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight() // Allow component to determine height
)
}
}
}- Debug with visibility logging:
DisposableEffect(component) {
Log.d("Payout", "Component mounted: ${component.componentType}")
onDispose {
Log.d("Payout", "Component disposed: ${component.componentType}")
}
}Causes:
- Callback not properly defined in config.
- Component not properly initialised.
- Error occurred before callback could be triggered.
Solutions:
- Verify callback syntax:
// Correct callback syntax
val config = PayoutSubmissionComponentConfig(
recipientWallet = "Paypal",
onPrePayoutSubmit = { // Lambda with no parameters
PrePayoutSubmitResult(isApproved = true)
},
onPostPayout = { result -> // Lambda with parameter
handleSuccess(result)
}
)- Add logging to all callbacks:
val config = PayoutSubmissionComponentConfig(
recipientWallet = "Paypal",
onClick = {
Log.d("Payout", "onClick triggered")
},
onPrePayoutSubmit = {
Log.d("Payout", "onPrePayoutSubmit triggered")
PrePayoutSubmitResult(isApproved = true)
},
onPostPayout = { result ->
Log.d("Payout", "onPostPayout triggered: ${result.merchantTransactionId}")
},
onError = { error ->
Log.e("Payout", "onError triggered: ${error.errorCode}")
}
)// Add logging throughout your payout flow
class PayoutActivity : ComponentActivity() {
private val TAG = "PayoutDebug"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Activity created")
try {
Log.d(TAG, "Initialising SDK")
val pxpCheckout = initializeSDK()
Log.d(TAG, "SDK initialised successfully")
Log.d(TAG, "Creating components")
val components = createComponents(pxpCheckout)
Log.d(TAG, "Components created: ${components.size}")
setContent {
MaterialTheme {
PayoutScreen(components)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error during initialisation", e)
showError("Initialisation failed: ${e.message}")
}
}
}onError = { error ->
// Log all available error information
Log.e("Payout", """
====== Payout Error ======
Error Code: ${error.errorCode}
Error Reason: ${error.errorReason}
HTTP Status: ${error.httpStatusCode}
Correlation ID: ${error.correlationId}
Details: ${error.details}
Stack Trace: ${Log.getStackTraceString(Exception())}
==========================
""".trimIndent())
// Send to crash reporting service
reportError(error)
}// Test with various amounts
testPayouts(
amounts = listOf(0.01, 1.0, 100.0, 9999.99),
currencies = listOf("USD", "EUR", "GBP")
)
// Test error scenarios
testErrorScenarios(
scenarios = listOf(
"zero_amount",
"invalid_currency",
"missing_wallet",
"network_failure"
)
)