Skip to content

Submission component

Add a withdrawal button for returning customers in your Android app.

Overview

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.

When to use

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.

Component creation

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
)

Configuration options

PayoutSubmissionComponentConfig

PropertyDescription
recipientWallet
String
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".
buttonText
String
The submit button text. Customise it to match your app's terminology. Defaults to "Withdraw with".
style
PayoutSubmissionStyle?
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.

Event callbacks

onClick

Triggered when the customer taps the withdrawal button.

onClick = {
    Log.d("Payout", "Withdrawal button tapped")
    trackEvent("payout_button_tapped")
    showLoadingIndicator()
}

onPrePayoutSubmit

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)
}

onPostPayout

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)
}

onError

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
    ))
}

Rendering the component

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()
    )
}

Complete example

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")
                    }
                }
            )
        }
    }
}

Best practices

Always implement onPrePayoutSubmit

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
}

Proper error handling

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.")
    }
}

Track analytics

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") }