Skip to content

Pay now flow

Capture funds instantly upon customer approval for Android.

Overview

The pay now flow is designed for immediate payment capture, making it ideal for digital products, services, or when instant payment confirmation is required.

Payment flow

The PayPal pay now flow consists of five key steps for immediate payment processing.

Step 1: Submission

The customer taps the PayPal button, which triggers the payment flow. The SDK validates the PayPal configuration and transaction data before proceeding to order creation. If validation fails, onError is triggered.

Step 2: Order creation

The SDK creates a PayPal order using the provided transaction details. This involves sending the payment amount, currency, merchant information, and any additional order details to PayPal's API. If order creation fails, onError is triggered.

Step 3: PayPal approval

The customer is redirected to PayPal (via WebView) where they can log in and approve the payment.

This step has three associated callbacks:

  • onSuccess: Proceeds with payment capture if the customer successfully approves the payment.
  • onCancel: Cancels the transaction if the customer cancels the payment.
  • onError: Receives error data if any error occurs during the approval process.

PayPal handles all authentication and payment method selection within their secure environment.

Step 4: Payment capture

Once the customer approves the payment, the SDK processes the payment immediately and automatically captures the funds. This happens within the onSuccess callback handler, where you process the capture and handle success/failure.

Step 5: Payment result

You receive the final payment result from PayPal and the onSuccess callback completes processing. The transaction is either completed successfully with payment confirmation, or fails with specific error details.

Implementation

Before you start

To use PayPal pay now payments in your Android application:

  1. Ensure you have a valid PayPal Business account with API credentials.
  2. Configure your PayPal merchant account to accept the currencies you need.
  3. Set up your merchant configuration in the Unity Portal (API credentials, payment methods, risk settings).
  4. Install and configure the PXP Checkout SDK for Android.

Step 1: Configure your SDK

Set up your SDK configuration with transaction information for PayPal payments. For pay now, use IntentType.Purchase, which maps to PayPal's capture intent.

Intent for Pay Now: For immediate payment capture (pay now flow), use IntentType.Purchase, which maps to PayPal's "capture" intent.

import com.pxp.checkout.PxpCheckout
import com.pxp.checkout.models.Environment
import com.pxp.checkout.models.OwnerType
import com.pxp.checkout.models.TransactionData
import com.pxp.checkout.models.EntryType
import com.pxp.checkout.models.IntentType
import com.pxp.checkout.models.Shopper
import com.pxp.checkout.models.ShippingAddress
import java.util.UUID

val pxpCheckout = PxpCheckout.builder()
    .withConfig(
        PxpCheckoutConfig(
            environment = Environment.TEST,
            session = sessionData,
            ownerId = "Unity",
            ownerType = OwnerType.MERCHANT_GROUP,
            transactionData = TransactionData(
                amount = 2500.0, // Amount in cents ($25.00)
                currency = "USD",
                entryType = EntryType.Ecom,
                intent = IntentType.Purchase,
                merchantTransactionId = UUID.randomUUID().toString(),
                merchantTransactionDate = { System.currentTimeMillis() }
            ),
            onGetShopper = {
                Shopper(
                    id = "Shopper_01",
                    email = "customer@example.com",
                    firstName = "John",
                    lastName = "Doe"
                )
            },
            onGetShippingAddress = {
                ShippingAddress(
                    addressLine1 = "123 Main Street",
                    city = "New York",
                    state = "NY",
                    postalCode = "10001",
                    countryCode = "US"
                )
            }
        )
    )
    .withContext(context)
    .build()

Step 2: Implement callbacks

Implement the required callbacks for PayPal pay now payments.

import com.pxp.checkout.components.paypal.PayPalComponentConfig
import com.pxp.checkout.components.paypal.types.ShippingPreference
import com.pxp.checkout.components.paypal.types.UserAction
import org.json.JSONObject
import android.util.Log
import kotlinx.coroutines.launch

val paypalConfig = PayPalComponentConfig(
    // Required PayPal configuration
    payeeEmailAddress = "merchant@example.com",
    paymentDescription = "Product purchase",
    shippingPreference = ShippingPreference.NoShipping.toString(),
    userAction = UserAction.PayNow.toString(),
    renderType = "standalone",
    fundingSources = "paypal",

    // REQUIRED: Handle successful payment approval
    onSuccess = { data ->
        Log.d("PayPal", "Payment approved: $data")
        
        try {
            // Parse the payment data
            val jsonObject = JSONObject(data)
            val orderID = jsonObject.getString("orderID")
            val payerID = jsonObject.getString("payerID")
            
            // Process the payment on your backend
            viewModelScope.launch {
                val result = repository.capturePayPalPayment(
                    orderID = orderID,
                    payerID = payerID,
                    merchantTransactionId = transactionId
                )
                
                if (result.isSuccess) {
                    Log.d("PayPal", "Payment captured successfully")
                    
                    // Navigate to success screen
                    navController.navigate("payment_success/$orderID")
                } else {
                    throw Exception(result.error ?: "Payment capture failed")
                }
            }
        } catch (e: Exception) {
            Log.e("PayPal", "Payment processing error", e)
            showError("Payment failed. Please try again.")
        }
    },

    // REQUIRED: Handle payment errors
    onError = { error ->
        Log.e("PayPal", "Payment error: $error")
        showError("Payment failed. Please try again.")
    },

    // OPTIONAL: Handle payment cancellation
    onCancel = {
        Log.d("PayPal", "Payment cancelled by user")
        showMessage("Payment was cancelled. You can try again anytime.")
    }
)

Step 3: Render the component

Create and render the PayPal component in your Compose UI.

import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.fillMaxWidth
import com.pxp.checkout.types.ComponentType
import com.pxp.checkout.PxpCheckout

@Composable
fun PayPalPaymentScreen(pxpCheckout: PxpCheckout, paypalConfig: PayPalComponentConfig) {
    val paypalComponent = remember {
        pxpCheckout.createComponent(
            type = ComponentType.PAYPAL,
            config = paypalConfig
        )
    }
    
    pxpCheckout.buildComponentView(
        component = paypalComponent,
        modifier = Modifier.fillMaxWidth()
    )
}

Step 4: Handle common scenarios

Amount-based processing

Use different processing logic based on transaction amounts.

val paypalConfig = PayPalComponentConfig(
    onSuccess = { data ->
        val amount = transactionData.amount
        
        // Add additional verification for high-value transactions
        if (amount > 10000) { // Over $100.00
            viewModelScope.launch {
                val confirmed = showConfirmationDialog(
                    "Confirm payment of $${amount / 100.0}?"
                )
                
                if (confirmed) {
                    processPayPalPayment(data)
                } else {
                    showMessage("Payment cancelled by user.")
                }
            }
        } else {
            processPayPalPayment(data)
        }
    }
)

Customer type handling

Handle different customer types with varying processing requirements.

fun getCustomerProcessingOptions(customerType: String): ProcessingOptions {
    return when (customerType) {
        "new" -> ProcessingOptions(
            verification = VerificationLevel.ENHANCED,
            emailConfirmation = true
        )
        "returning" -> ProcessingOptions(
            verification = VerificationLevel.STANDARD,
            emailConfirmation = false
        )
        "vip" -> ProcessingOptions(
            verification = VerificationLevel.MINIMAL,
            fastProcessing = true
        )
        else -> ProcessingOptions(
            verification = VerificationLevel.STANDARD,
            emailConfirmation = true
        )
    }
}

val paypalConfig = PayPalComponentConfig(
    onSuccess = { data ->
        val processingOptions = getCustomerProcessingOptions("returning")
        
        viewModelScope.launch {
            processPayPalPayment(
                data = data,
                options = processingOptions,
                customerType = "returning",
                timestamp = System.currentTimeMillis()
            )
        }
    }
)

Step 5: Handle errors

Implement comprehensive error handling for PayPal payments.

val paypalConfig = PayPalComponentConfig(
    onError = { error ->
        Log.e("PayPal", "Error: $error")
        
        // Handle specific PayPal error types
        when {
            error.contains("VALIDATION_ERROR") -> {
                showErrorDialog(
                    title = "Validation Error",
                    message = "Payment details validation failed. Please try again."
                )
            }
            error.contains("INSTRUMENT_DECLINED") -> {
                showErrorDialog(
                    title = "Payment Declined",
                    message = "Your PayPal payment method was declined. Please try a different method."
                )
            }
            error.contains("PAYER_ACTION_REQUIRED") -> {
                showErrorDialog(
                    title = "Action Required",
                    message = "Additional action required in PayPal. Please complete the payment process."
                )
            }
            error.contains("UNPROCESSABLE_ENTITY") -> {
                showErrorDialog(
                    title = "Processing Error",
                    message = "Payment cannot be processed. Please contact support."
                )
            }
            else -> {
                showErrorDialog(
                    title = "Payment Error",
                    message = "Payment failed. Please try again or contact support."
                )
            }
        }
    },

    onSuccess = { data ->
        try {
            processPayPalPayment(data)
        } catch (e: Exception) {
            // Handle backend processing errors
            when {
                e is HttpException && e.code() == 422 -> {
                    showError("Payment details could not be verified. Please try again.")
                }
                e is HttpException && e.code() == 409 -> {
                    showError("This payment has already been processed.")
                }
                e is HttpException && e.code() >= 500 -> {
                    showError("Payment system temporarily unavailable. Please try again later.")
                }
                else -> {
                    showError("Payment processing failed. Please try again.")
                }
            }
        }
    }
)

Example

The following example shows a complete PayPal pay now implementation with Jetpack Compose.

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 androidx.lifecycle.viewModelScope
import com.pxp.checkout.PxpCheckout
import com.pxp.checkout.components.paypal.PayPalComponentConfig
import com.pxp.checkout.components.paypal.types.ShippingPreference
import com.pxp.checkout.components.paypal.types.UserAction
import com.pxp.checkout.components.paypal.types.PayPalButtonStyle
import com.pxp.checkout.types.ComponentType
import kotlinx.coroutines.launch
import org.json.JSONObject
import android.util.Log

@Composable
fun PayPalPayNowScreen(
    pxpCheckout: PxpCheckout,
    viewModel: PaymentViewModel
) {
    var showLoadingSpinner by remember { mutableStateOf(false) }
    var showSuccessDialog by remember { mutableStateOf(false) }
    var showErrorDialog by remember { mutableStateOf(false) }
    var paymentData by remember { mutableStateOf<PaymentResult?>(null) }
    var errorMessage by remember { mutableStateOf<String?>(null) }
    
    val paypalConfig = remember {
        PayPalComponentConfig(
            // PayPal configuration
            payeeEmailAddress = "merchant@example.com",
            paymentDescription = "Product Purchase - $25.00",
            shippingPreference = ShippingPreference.NoShipping.toString(),
            userAction = UserAction.PayNow.toString(),
            renderType = "standalone",
            fundingSources = "paypal",
            
            // Styling
            style = PayPalButtonStyle(
                layout = "vertical",
                color = "gold",
                shape = "rect",
                label = "paypal"
            ),

            // Step 1: Handle payment approval
            onSuccess = { data ->
                Log.d("PayPal", "Processing PayPal payment")
                showLoadingSpinner = true
                
                try {
                    // Log transaction details
                    val jsonObject = JSONObject(data)
                    val orderID = jsonObject.getString("orderID")
                    val payerID = jsonObject.getString("payerID")
                    
                    Log.d("PayPal", "Order ID: $orderID")
                    Log.d("PayPal", "Payer ID: $payerID")
                    
                    // Process payment on backend
                    viewModel.viewModelScope.launch {
                        val result = viewModel.capturePayment(
                            orderID = orderID,
                            payerID = payerID
                        )
                        
                        showLoadingSpinner = false
                        
                        if (result.isSuccess) {
                            Log.d("PayPal", "Payment completed successfully")
                            paymentData = result.data
                            showSuccessDialog = true
                        } else {
                            errorMessage = result.error ?: "Payment capture failed"
                            showErrorDialog = true
                        }
                    }
                } catch (e: Exception) {
                    Log.e("PayPal", "Payment processing failed", e)
                    showLoadingSpinner = false
                    errorMessage = e.message ?: "Please try again"
                    showErrorDialog = true
                }
            },

            // Step 2: Handle cancellation
            onCancel = {
                Log.d("PayPal", "Payment cancelled by user")
                showSnackbar("Payment was cancelled. Your order is still available.")
            },

            // Step 3: Handle errors
            onError = { error ->
                Log.e("PayPal", "Payment error: $error")
                showLoadingSpinner = false
                errorMessage = error
                showErrorDialog = true
            }
        )
    }
    
    val paypalComponent = remember(paypalConfig) {
        pxpCheckout.createComponent(
            type = ComponentType.PAYPAL,
            config = paypalConfig
        )
    }

    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text(
                text = "Complete your payment",
                style = MaterialTheme.typography.headlineMedium
            )
            
            Text(
                text = "Amount: $25.00",
                style = MaterialTheme.typography.titleMedium
            )
            
            // PayPal button
            pxpCheckout.buildComponentView(
                component = paypalComponent,
                modifier = Modifier.fillMaxWidth()
            )
            
            // Loading indicator
            if (showLoadingSpinner) {
                CircularProgressIndicator(
                    modifier = Modifier.size(48.dp)
                )
            }
        }
    }
    
    // Success dialog
    if (showSuccessDialog) {
        AlertDialog(
            onDismissRequest = { },
            title = { Text("Payment Successful") },
            text = { 
                Text("Your payment has been processed successfully!")
            },
            confirmButton = {
                Button(onClick = {
                    showSuccessDialog = false
                    // Navigate or finish
                }) {
                    Text("OK")
                }
            }
        )
    }
    
    // Error dialog
    if (showErrorDialog) {
        AlertDialog(
            onDismissRequest = { },
            title = { Text("Payment Error") },
            text = { 
                Text(errorMessage ?: "An error occurred")
            },
            confirmButton = {
                Button(onClick = { showErrorDialog = false }) {
                    Text("OK")
                }
            }
        )
    }
}

Callback data

This section describes the data received by the different callbacks as part of the PayPal pay now flow.

onSuccess

The onSuccess callback receives payment approval data when the customer successfully approves the payment in PayPal. The approval data includes the PayPal order ID and payer information needed to capture the payment.

{
  "orderID": "7YH53119ML8957234",
  "payerID": "ABCDEFGHIJKLM",
  "paymentID": "PAYID-ABCDEFG",
  "billingToken": null,
  "facilitatorAccessToken": "A21AAFExi..."
}
ParameterDescription
orderID
String
required
The unique PayPal order ID that identifies this payment.
payerID
String
required
The unique PayPal payer ID that identifies the customer who approved the payment.
paymentID
String
The PayPal payment ID for this transaction.
billingToken
String?
The billing agreement token if applicable, otherwise null.
facilitatorAccessToken
String
The PayPal facilitator access token for processing the payment.

Here's an example of what to do with this data:

onSuccess = { data ->
    Log.d("PayPal", "Payment approved: $data")
    
    try {
        // Parse the payment data
        val jsonObject = JSONObject(data)
        val orderID = jsonObject.getString("orderID")
        val payerID = jsonObject.getString("payerID")
        
        // Capture the payment immediately for pay now flow
        viewModelScope.launch {
            val captureResponse = repository.capturePayPalPayment(
                orderID = orderID,
                payerID = payerID,
                merchantTransactionId = generateTransactionId(),
                timestamp = System.currentTimeMillis()
            )
            
            if (captureResponse.status == "COMPLETED") {
                // Payment captured successfully
                Log.d("PayPal", "Payment captured: ${captureResponse.id}")
                
                // Store transaction record
                database.insertTransaction(
                    Transaction(
                        paypalOrderId = orderID,
                        paypalPayerId = payerID,
                        transactionId = captureResponse.id,
                        amount = captureResponse.amount,
                        currency = captureResponse.currency,
                        status = "completed",
                        timestamp = System.currentTimeMillis()
                    )
                )
                
                // Navigate to success
                navController.navigate("success/$orderID")
            } else {
                throw Exception("Payment capture failed: ${captureResponse.status}")
            }
        }
    } catch (e: Exception) {
        Log.e("PayPal", "Payment capture error", e)
        showError("Payment processing failed. Please contact support.")
    }
}

onError

The onError callback receives error information when PayPal payments fail. PayPal errors include specific error names and details to help with troubleshooting.

{
  "name": "VALIDATION_ERROR",
  "message": "Invalid payment method",
  "details": [{
    "issue": "INSTRUMENT_DECLINED",
    "description": "The instrument presented was either declined by the processor or bank, or it can't be used for this payment."
  }]
}
ParameterDescription
name
String
The error name.

Possible values:
  • VALIDATION_ERROR
  • INSTRUMENT_DECLINED
  • PAYER_ACTION_REQUIRED
  • UNPROCESSABLE_ENTITY
message
String
A human-readable error message.

Here's an example of how to handle PayPal errors:

onError = { error ->
    Log.e("PayPal", "Payment error: $error")
    
    // Handle specific error types
    when {
        error.contains("VALIDATION_ERROR") -> {
            showErrorDialog(
                title = "Validation Error",
                message = "Payment information is invalid. Please try again."
            )
        }
        error.contains("INSTRUMENT_DECLINED") -> {
            showErrorDialog(
                title = "Payment Declined",
                message = "Your PayPal payment method was declined. Please try a different payment method."
            )
        }
        error.contains("PAYER_ACTION_REQUIRED") -> {
            showErrorDialog(
                title = "Action Required",
                message = "Additional verification required. Please complete the process in PayPal."
            )
        }
        error.contains("UNPROCESSABLE_ENTITY") -> {
            showErrorDialog(
                title = "Processing Error",
                message = "This payment cannot be processed. Please contact support."
            )
        }
        error.contains("INTERNAL_SERVICE_ERROR") -> {
            showErrorDialog(
                title = "Service Error",
                message = "PayPal service temporarily unavailable. Please try again later."
            )
        }
        else -> {
            showErrorDialog(
                title = "Payment Error",
                message = "Payment failed. Please try again or contact support."
            )
        }
    }
    
    // Log error details for monitoring
    analytics.track("paypal_payment_error", mapOf(
        "errorMessage" to error,
        "timestamp" to System.currentTimeMillis(),
        "paymentMethod" to "paypal"
    ))
}

onCancel

The onCancel callback receives no data when the customer cancels the PayPal payment process.

Here's an example of how to handle cancellations:

onCancel = {
    Log.d("PayPal", "Payment cancelled by user")
    
    // Log cancellation for analytics
    analytics.track("paypal_payment_cancelled", mapOf(
        "timestamp" to System.currentTimeMillis(),
        "paymentMethod" to "paypal"
    ))
    
    // Show user-friendly message
    showSnackbar("Payment was cancelled. Your cart items are still saved.")
    
    // Optional: Offer alternative payment methods
    showAlternativePaymentOptions()
}