Add a withdrawal button for returning customers in your Android app.
The payout submission component provides a "Withdraw with PayPal" button that executes payouts for returning customers. It's designed for the withdrawal flow when you already have the customer's wallet details stored.
Use the submission component when:
- You're implementing the withdrawal flow for returning customers.
- You have the customer's PayPal email and payer ID stored.
- You want to provide a streamlined payout experience without requiring login.
- You need full control over the payout approval process.
Create the submission component using the SDK's factory method:
import com.pxp.checkout.components.payoutsubmission.PayoutSubmissionComponentConfig
import com.pxp.checkout.models.payout.PrePayoutSubmitResult
import com.pxp.checkout.types.ComponentType
val submitConfig = PayoutSubmissionComponentConfig(
recipientWallet = "Paypal",
onPrePayoutSubmit = {
val confirmed = showConfirmationDialog()
PrePayoutSubmitResult(isApproved = confirmed)
},
onPostPayout = { result ->
navigateToSuccessScreen(result.merchantTransactionId)
}
)
val submitComponent = pxpCheckout.createComponent(
type = ComponentType.PAYOUT_SUBMISSION,
config = submitConfig
)| Property | Description |
|---|---|
recipientWalletString | The recipient wallet type. Only "Paypal" is currently supported. Empty or blank values default to "Paypal". Component validation will throw an error for other non-empty values. Receiver email is obtained from sdkConfig.paypalConfig.payout.paypalWallet.email. Defaults to "Paypal". |
buttonTextString | The submit button text. Customise it to match your app's terminology. Defaults to "Withdraw with". |
stylePayoutSubmissionStyle? | The submit button styling. Customise colours, typography, and spacing. Uses default PayPal styling if not provided. Defaults to null. |
onClick(() -> Unit)? | Called when the submit button is tapped. Use for analytics tracking, show loading indicators, or perform pre-validation checks. Defaults to null. |
onPrePayoutSubmit(suspend () -> PrePayoutSubmitResult)? | Called before payout submission. Show confirmation dialog and perform validation checks. This is a suspend function where the component waits for approval. Return PrePayoutSubmitResult with isApproved status. Defaults to null. |
onPostPayout((PostPayoutResult) -> Unit)? | Called when payout succeeds. Receives transaction identifiers. Update your records, show success message, and navigate to confirmation screen. Defaults to null. |
onError((PayOutError) -> Unit)? | Called when payout fails. Receives error details with code and reason. May include transaction IDs if transaction was created. Display error messages and implement retry logic. Defaults to null. |
Triggered when the customer taps the withdrawal button.
onClick = {
Log.d("Payout", "Withdrawal button tapped")
trackEvent("payout_button_tapped")
showLoadingIndicator()
}Triggered after tap and validation, before payout execution. The component suspends and waits for approval.
Return value:
isApproved(Boolean, required): Whether to proceed with the payout.
onPrePayoutSubmit = {
// Show confirmation dialog
val confirmed = showConfirmationDialog(
title = "Confirm Withdrawal",
message = "Withdraw $$amount to your PayPal account?"
)
if (!confirmed) {
return@PayoutSubmissionComponentConfig PrePayoutSubmitResult(isApproved = false)
}
// Perform validation
val balanceCheck = verifyBalance()
if (!balanceCheck.sufficient) {
showError("Insufficient balance")
return@PayoutSubmissionComponentConfig PrePayoutSubmitResult(isApproved = false)
}
// Approve the payout
PrePayoutSubmitResult(isApproved = true)
}Triggered when payout completes successfully.
Parameters:
data.merchantTransactionId(String?): Your transaction identifier (optional).data.systemTransactionId(String): The system transaction identifier.
onPostPayout = { result ->
Log.d("Payout", "Payout successful: ${result.systemTransactionId}")
// Update records
storeTransaction(
merchantTransactionId = result.merchantTransactionId,
systemTransactionId = result.systemTransactionId
)
// Show success
showSuccessMessage("$amount has been sent to your PayPal account!")
// Navigate
navigateToSuccessScreen(result.merchantTransactionId)
}Triggered when payout fails.
Parameters:
error.errorCode(String?): Error code identifier.error.errorReason(String?): Human-readable error message.error.httpStatusCode(Int?): HTTP status code.error.correlationId(String?, optional): Correlation ID for tracking.error.details(List<String?>?, optional): Additional error details.
onError = { error ->
Log.e("Payout", "Payout failed: ${error.errorReason}")
// Handle specific error types
when (error.errorCode) {
"PO04" -> showError("Invalid recipient information.")
"PO02" -> showError("Invalid payout amount. Please contact support.")
else -> showError("An error occurred. Please try again.")
}
// Log for debugging
logError("payout_error", mapOf(
"errorCode" to error.errorCode,
"correlationId" to error.correlationId
))
}Render the component using Jetpack Compose:
@Composable
fun WithdrawalScreen() {
val submitComponent = remember {
pxpCheckout.createComponent(
type = ComponentType.PAYOUT_SUBMISSION,
config = submitConfig
)
}
pxpCheckout.buildComponentView(
component = submitComponent,
modifier = Modifier.fillMaxWidth()
)
}Here's a complete example showing the submission component in a withdrawal flow:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pxp.checkout.PxpCheckout
import com.pxp.checkout.components.payoutamount.PayoutAmountComponentConfig
import com.pxp.checkout.components.paypalpayoutreceiver.PaypalPayoutReceiverComponentConfig
import com.pxp.checkout.components.payoutsubmission.PayoutSubmissionComponentConfig
import com.pxp.checkout.models.*
import com.pxp.checkout.models.payout.PrePayoutSubmitResult
import com.pxp.checkout.types.ComponentType
import java.util.UUID
class PayoutSubmissionActivity : ComponentActivity() {
private lateinit var pxpCheckout: PxpCheckout
private val payoutAmount = 150.0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get stored customer wallet
val customerWallet = getStoredCustomerWallet()
// Initialise SDK with stored wallet details
pxpCheckout = PxpCheckout.builder()
.withConfig(
PxpSdkConfig(
environment = Environment.TEST,
session = sessionData,
ownerId = "Unity",
ownerType = "MerchantGroup",
clientId = "your-client-id",
transactionData = TransactionData(
amount = payoutAmount,
currency = "USD",
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,
paypalWallet = PayPalPayOutWalletConfig(
email = customerWallet.email,
payerId = customerWallet.payerId
)
)
)
)
)
.withContext(this)
.build()
setContent {
MaterialTheme {
WithdrawalScreen()
}
}
}
@Composable
fun WithdrawalScreen() {
var payoutStatus by remember { mutableStateOf<String?>(null) }
var showSuccessDialog by remember { mutableStateOf(false) }
// Create components
val amountComponent = remember {
pxpCheckout.createComponent(
type = ComponentType.PAYOUT_AMOUNT,
config = PayoutAmountComponentConfig(
label = "Withdrawal Amount"
)
)
}
val receiverComponent = remember {
pxpCheckout.createComponent(
type = ComponentType.PAYPAL_PAYOUT_RECEIVER,
config = PaypalPayoutReceiverComponentConfig(
label = "Sending to",
showMaskToggle = true,
applyMask = true
)
)
}
val submitConfig = remember {
PayoutSubmissionComponentConfig(
recipientWallet = "Paypal",
buttonText = "Withdraw to PayPal",
onClick = {
Log.d("Payout", "Button tapped")
trackEvent("payout_button_tapped")
},
onPrePayoutSubmit = {
Log.d("Payout", "Pre-payout validation")
// Check balance
val balance = checkUserBalance()
if (balance < payoutAmount) {
showError("Insufficient balance")
return@PayoutSubmissionComponentConfig PrePayoutSubmitResult(isApproved = false)
}
// Show confirmation
val confirmed = showConfirmationDialog(
title = "Confirm Withdrawal",
message = "Withdraw $${"%.2f".format(payoutAmount)} to your PayPal account?",
details = listOf("Processing time: 1-2 business days")
)
if (!confirmed) {
trackEvent("payout_cancelled")
PrePayoutSubmitResult(isApproved = false)
} else {
trackEvent("payout_approved")
PrePayoutSubmitResult(isApproved = true)
}
},
onPostPayout = { result ->
Log.d("Payout", "Payout successful: ${result.systemTransactionId}")
trackEvent("payout_completed", mapOf(
"merchantTransactionId" to result.merchantTransactionId,
"systemTransactionId" to result.systemTransactionId,
"amount" to payoutAmount
))
updateUserBalance(balance - payoutAmount)
payoutStatus = "Withdrawal completed successfully!"
showSuccessDialog = true
},
onError = { error ->
Log.e("Payout", "Payout error: ${error.errorReason}")
trackEvent("payout_failed", mapOf(
"errorCode" to error.errorCode,
"httpStatusCode" to error.httpStatusCode.toString()
))
payoutStatus = "Error: ${error.errorReason}"
}
)
}
val submitComponent = remember {
pxpCheckout.createComponent(
type = ComponentType.PAYOUT_SUBMISSION,
config = submitConfig
)
}
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Withdraw funds",
style = MaterialTheme.typography.headlineMedium
)
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
pxpCheckout.buildComponentView(
component = amountComponent,
modifier = Modifier.fillMaxWidth()
)
Divider()
pxpCheckout.buildComponentView(
component = receiverComponent,
modifier = Modifier.fillMaxWidth()
)
Divider()
pxpCheckout.buildComponentView(
component = submitComponent,
modifier = Modifier.fillMaxWidth()
)
}
}
TextButton(onClick = { navigateToChangePayoutMethod() }) {
Text("Use a different PayPal account?")
}
payoutStatus?.let { status ->
Text(
text = status,
style = MaterialTheme.typography.bodyMedium,
color = if (status.contains("Error")) {
MaterialTheme.colorScheme.error
} else {
MaterialTheme.colorScheme.primary
}
)
}
}
}
if (showSuccessDialog) {
AlertDialog(
onDismissRequest = { showSuccessDialog = false },
title = { Text("Withdrawal Successful") },
text = { Text(payoutStatus ?: "") },
confirmButton = {
Button(onClick = {
showSuccessDialog = false
finish()
}) {
Text("OK")
}
}
)
}
}
}Use this callback to confirm the payout with the customer:
// Good: Confirm before payout
onPrePayoutSubmit = {
val confirmed = showConfirmationDialog()
PrePayoutSubmitResult(isApproved = confirmed)
}
// Less ideal: No confirmation
onPrePayoutSubmit = {
PrePayoutSubmitResult(isApproved = true) // Risky without user confirmation
}Handle all error types gracefully:
onError = { error ->
when (error.errorCode) {
"PO04" -> showError("Invalid recipient information.")
"PO02" -> showError("Invalid payout amount. Please contact support.")
"PO03" -> showError("Invalid currency.")
"PO07" -> showError("Invalid transaction date.")
else -> showError("An error occurred. Please try again.")
}
}Implement comprehensive tracking:
onClick = { trackEvent("payout_button_tapped") }
onPrePayoutSubmit = {
if (approved) trackEvent("payout_approved")
else trackEvent("payout_cancelled")
//...
}
onPostPayout = { trackEvent("payout_completed") }
onError = { trackEvent("payout_failed") }