{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","admonition","br","details"]},"type":"markdown"},"seo":{"title":"Continue flow","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":"continue-flow","__idx":0},"children":["Continue flow"]},{"$$mdtype":"Tag","name":"SubHeading","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Display an order review page before capturing payment."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The continue flow is a two-step checkout experience that allows customers to review their order details after PayPal approval but before final payment submission. This flow is ideal for e-commerce checkouts requiring shipping confirmation, complex orders needing customer review, or compliance requirements mandating order confirmation."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To enable the continue flow, you ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["must"]}," set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["userAction = UserAction.Continue"]}," in the component configuration. Without this setting, the button defaults to the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/components/android/paypal/pay-now"},"children":["pay now flow"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"payment-flow","__idx":2},"children":["Payment flow"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The PayPal continue flow consists of six key steps with approval and capture separation."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-submission","__idx":3},"children":["Step 1: Submission"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]}," is triggered."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-order-creation","__idx":4},"children":["Step 2: Order creation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK creates a PayPal order using the provided transaction details. This involves sending the payment amount, currency, merchant information, shipping options, and any additional order details to PayPal's API. If order creation fails, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]}," is triggered."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-3-paypal-approval","__idx":5},"children":["Step 3: PayPal approval"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The customer is redirected to PayPal checkout where they can log in, select their payment method, and optionally choose a shipping address."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When the customer taps the PayPal button (with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["userAction"]}," set to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Continue"]},"), the PayPal authentication flow opens and the customer enters their credentials."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After login, the customer is requested to review the order. The text shown in the PayPal user interface may vary; sometimes it's displayed as \"Continue to Review Order\"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This step has five associated callbacks:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onApprove"]},": Retrieves order details and navigates to the review page if the customer successfully approves the order."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onCancel"]},": Cancels the transaction if the customer cancels in PayPal."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]},": Receives error data if any error occurs during the approval process."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onShippingAddressChange"]},": Validates shipping addresses as the customer changes them in PayPal."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onShippingOptionsChange"]},": Handles shipping option changes made by the customer."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["PayPal handles all authentication and payment method selection within their secure environment."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-4-return-to-merchant","__idx":6},"children":["Step 4: Return to merchant"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After PayPal approval, the customer is returned to your app with the approved order. The customer can review their order details including contact information, shipping address, and order summary before final submission."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-5-order-review","__idx":7},"children":["Step 5: Order review"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["On the review screen, the customer can see:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Contact information:"]}," Name, email, and phone number from PayPal"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Shipping address:"]}," Full delivery address selected in PayPal"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Order summary:"]}," Itemised breakdown of subtotal, shipping, tax, and total"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The customer can either submit the order to complete their payment or cancel and return to the checkout screen."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-6-payment-capture","__idx":8},"children":["Step 6: Payment capture"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once the customer confirms their order on the review screen, you capture the payment. This is when the funds are actually transferred from the customer's account."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your capture API call will return a success or failure response, which you handle in your backend code."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"implementation","__idx":9},"children":["Implementation"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"before-you-start","__idx":10},"children":["Before you start"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To use PayPal continue flow in your Android application:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Ensure you have a valid PayPal Business account with API credentials."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Configure your PayPal merchant account to accept the currencies you need."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Set up your merchant configuration in the Unity Portal (API credentials, payment methods, risk settings)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Implement a review screen to display order details."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Create a backend endpoint to capture payments after customer confirmation."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-configure-your-sdk","__idx":11},"children":["Step 1: Configure your SDK"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set up your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PxpCheckout"]}," instance with transaction information for PayPal payments."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val transactionData = TransactionData(\n    amount = 2500.0,\n    currency = \"USD\",\n    merchant = \"your-merchant-id\",\n    entryType = EntryType.Ecom,\n    intent = TransactionIntentData(\n        paypal = IntentType.Authorisation\n    ),\n    merchantTransactionId = \"order-${System.currentTimeMillis()}\",\n    merchantTransactionDate = { Clock.System.now().toString() },\n    cardAcceptorName = \"Your Store Name\"\n)\n\nval sdkConfig = PxpSdkConfig(\n    environment = Environment.TEST, // or Environment.LIVE for production\n    session = SessionConfig(\n        sessionId = \"your-session-id\",\n        hmacKey = \"your-hmac-key\",\n        data = \"your-session-data\",\n        encryptionKey = \"your-encryption-key\",\n        locale = \"en-US\"\n    ),\n    transactionData = transactionData,\n    clientId = \"your-client-id\",\n    ownerId = \"your-owner-id\",\n    ownerType = \"MerchantGroup\",\n    onGetShopper = {\n        // Return current shopper data dynamically\n        Shopper(\n            id = \"shopper-123\"\n        )\n    },\n    onGetShippingAddress = {\n        // Return current shipping address when needed\n        ShippingAddress(\n            addressLine1 = \"123 Main Street\",\n            addressLine2 = \"Apt 1\",\n            city = \"New York\",\n            county = \"NY\",\n            postalCode = \"10001\",\n            countryCode = \"US\"\n        )\n    }\n)\n\nval pxpCheckout = PxpCheckout.builder()\n    .withConfig(sdkConfig)\n    .withContext(context)\n    .build()\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-create-the-continue-button","__idx":12},"children":["Step 2: Create the continue button"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create the PayPal button with the continue flow configuration. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["userAction = UserAction.Continue"]}," setting is required to enable the continue flow."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Android PayPal callbacks receive JSON strings, not typed objects. You'll need to parse the JSON data in your callbacks. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," callback receives a JSON string containing the order approval data."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.pxp.checkout.components.paypal.types.UserAction\nimport com.pxp.checkout.components.paypal.types.ShippingPreference\nimport org.json.JSONObject\n\nval paypalConfig = PayPalComponentConfig().apply {\n    // Required PayPal configuration\n    renderType = \"setOfButtons\"\n    payeeEmailAddress = \"merchant@example.com\"\n    paymentDescription = \"Order Payment\"\n    \n    // REQUIRED: Enable continue flow (two-step checkout)\n    userAction = UserAction.Continue.toString()\n    \n    // Shipping configuration\n    shippingPreference = ShippingPreference.GetFromFile.toString()\n    shippingOptions = listOf(\n        ShippingOption(\n            id = \"1\",\n            label = \"Standard Shipping\",\n            selected = true,\n            amounts = ShippingAmounts(\n                currencyCode = \"USD\",\n                shipping = 10.00\n            ),\n            type = \"Shipping\"\n        ),\n        ShippingOption(\n            id = \"2\",\n            label = \"Express Shipping\",\n            selected = false,\n            amounts = ShippingAmounts(\n                currencyCode = \"USD\",\n                shipping = 15.00\n            ),\n            type = \"Shipping\"\n        )\n    )\n    \n    // Display options\n    locale = \"en-US\"\n    fundingSources = listOf(\"paypal\")\n    \n    // Styling\n    style = PayPalButtonStyle(\n        layout = \"vertical\",\n        color = \"gold\",\n        shape = \"rect\",\n        label = \"paypal\",\n        tagline = false,\n        height = 55\n    )\n\n    // REQUIRED: Handle order approval\n    onSuccess = { dataJson ->\n        Log.d(\"PayPal\", \"PayPal order approved: $dataJson\")\n        \n        try {\n            // Parse the JSON response and navigate to review screen\n            val orderData = parsePayPalResponse(dataJson)\n            navigateToReviewScreen(orderData)\n        } catch (e: Exception) {\n            Log.e(\"PayPal\", \"Error parsing order details\", e)\n            showError(\"Unable to retrieve order details. Please try again.\")\n        }\n    }\n\n    // REQUIRED: Handle payment errors\n    onError = { error ->\n        Log.e(\"PayPal\", \"PayPal payment error\", error)\n        showError(\"Payment failed. Please try again.\")\n    }\n\n    // OPTIONAL: Handle payment cancellation\n    onCancel = {\n        Log.d(\"PayPal\", \"PayPal payment cancelled\")\n        showMessage(\"Payment was cancelled. You can try again anytime.\")\n    }\n    \n    // OPTIONAL: Validate shipping address changes\n    onShippingAddressChange = { dataJson ->\n        Log.d(\"PayPal\", \"Shipping address changed: $dataJson\")\n        \n        try {\n            // Parse the JSON data to check country code\n            val jsonObject = JSONObject(dataJson)\n            val shippingAddress = jsonObject.optJSONObject(\"shippingAddress\")\n            \n            if (shippingAddress != null) {\n                val countryCode = shippingAddress.optString(\"countryCode\", \"\")\n                \n                // Validate country\n                if (countryCode != \"US\") {\n                    // Return reject action with error code\n                    return@onShippingAddressChange \"reject:COUNTRY_ERROR\"\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(\"PayPal\", \"Error parsing shipping address\", e)\n        }\n        \n        // Return null to allow the change\n        null\n    }\n    \n    // OPTIONAL: Handle shipping option changes\n    onShippingOptionsChange = { dataJson ->\n        Log.d(\"PayPal\", \"Shipping option changed: $dataJson\")\n        null\n    }\n}\n\n// Create the component\nval paypalComponent = pxpCheckout.createComponent<PayPalComponent, PayPalComponentConfig>(\n    type = ComponentType.PAYPAL,\n    config = paypalConfig\n)\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-3-implement-the-review-screen","__idx":13},"children":["Step 3: Implement the review screen"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a review screen composable to display order details after PayPal approval."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"@Composable\nfun ReviewOrderScreen(\n    orderData: OrderData,\n    onSubmit: (OrderData) -> Unit,\n    onCancel: () -> Unit\n) {\n    val payer = orderData.payer\n    val purchaseUnit = orderData.purchaseUnits.first()\n    val shipping = purchaseUnit.shipping\n    val amount = purchaseUnit.amount\n\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(16.dp)\n            .verticalScroll(rememberScrollState())\n    ) {\n        Text(\n            text = \"Review your order\",\n            style = MaterialTheme.typography.headlineMedium,\n            modifier = Modifier.padding(bottom = 8.dp)\n        )\n        \n        Text(\n            text = \"Please review your order details before completing the purchase\",\n            style = MaterialTheme.typography.bodyMedium,\n            modifier = Modifier.padding(bottom = 24.dp)\n        )\n\n        // Contact information\n        Card(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 16.dp)\n        ) {\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = \"Contact information\",\n                    style = MaterialTheme.typography.titleMedium,\n                    modifier = Modifier.padding(bottom = 8.dp)\n                )\n                Text(\"Name: ${payer.name.givenName} ${payer.name.surname}\")\n                Text(\"Email: ${payer.emailAddress}\")\n                payer.phone?.phoneNumber?.nationalNumber?.let {\n                    Text(\"Phone: $it\")\n                }\n            }\n        }\n\n        // Shipping address\n        Card(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 16.dp)\n        ) {\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = \"Delivering to\",\n                    style = MaterialTheme.typography.titleMedium,\n                    modifier = Modifier.padding(bottom = 8.dp)\n                )\n                Text(\n                    text = shipping.name.fullName,\n                    fontWeight = FontWeight.Bold\n                )\n                Text(shipping.address.addressLine1)\n                shipping.address.addressLine2?.let { Text(it) }\n                Text(\"${shipping.address.adminArea2}, ${shipping.address.adminArea1} ${shipping.address.postalCode}\")\n                Text(shipping.address.countryCode)\n            }\n        }\n\n        // Order summary\n        Card(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 16.dp)\n        ) {\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = \"Order summary\",\n                    style = MaterialTheme.typography.titleMedium,\n                    modifier = Modifier.padding(bottom = 8.dp)\n                )\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween\n                ) {\n                    Text(\"Subtotal:\")\n                    Text(\"${amount.currencyCode} ${amount.breakdown.itemTotal.value}\")\n                }\n                amount.breakdown.shipping?.value?.let {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        Text(\"Delivery:\")\n                        Text(\"${amount.currencyCode} $it\")\n                    }\n                }\n                amount.breakdown.taxTotal?.value?.let {\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        Text(\"Tax:\")\n                        Text(\"${amount.currencyCode} $it\")\n                    }\n                }\n                HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween\n                ) {\n                    Text(\n                        text = \"Total:\",\n                        fontWeight = FontWeight.Bold\n                    )\n                    Text(\n                        text = \"${amount.currencyCode} ${amount.value}\",\n                        fontWeight = FontWeight.Bold\n                    )\n                }\n            }\n        }\n\n        // Actions\n        Text(\n            text = \"Order ID: ${orderData.orderId}\",\n            style = MaterialTheme.typography.bodySmall,\n            modifier = Modifier.padding(bottom = 16.dp)\n        )\n        \n        Button(\n            onClick = { onSubmit(orderData) },\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(bottom = 8.dp)\n        ) {\n            Text(\"Submit Order & Pay Now\")\n        }\n        \n        OutlinedButton(\n            onClick = onCancel,\n            modifier = Modifier.fillMaxWidth()\n        ) {\n            Text(\"Cancel and go back\")\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-4-handle-order-submission","__idx":14},"children":["Step 4: Handle order submission"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the backend capture logic when the customer submits the order from the review screen."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"// In your ViewModel or Repository\nsuspend fun handleSubmitOrder(orderData: OrderData): Result<CaptureResponse> {\n    return withContext(Dispatchers.IO) {\n        try {\n            // Call backend to capture the PayPal order\n            val response = apiService.completeOrder(\n                orderId = orderData.orderId,\n                sessionId = sessionData.sessionId,\n                merchantTransactionId = transactionData.merchantTransactionId\n            )\n            \n            if (response.isSuccessful) {\n                Log.d(\"PayPal\", \"Payment captured successfully\")\n                Result.success(response.body()!!)\n            } else {\n                Result.failure(Exception(response.errorBody()?.string() ?: \"Payment capture failed\"))\n            }\n        } catch (e: Exception) {\n            Log.e(\"PayPal\", \"Order submission error\", e)\n            Result.failure(e)\n        }\n    }\n}\n\n// Backend endpoint (example using Ktor)\nsuspend fun completePaypalOrder(call: ApplicationCall) {\n    val request = call.receive<CompleteOrderRequest>()\n    \n    try {\n        // Call the PXP API to capture the PayPal order\n        val response = pxpApiClient.post(\"api/v1/paypal/orders/${request.orderId}/capture\") {\n            contentType(ContentType.Application.Json)\n            setBody(\n                mapOf(\n                    \"sessionId\" to request.sessionId,\n                    \"merchantTransactionId\" to request.merchantTransactionId\n                )\n            )\n        }\n        \n        call.respond(\n            HttpStatusCode.OK,\n            mapOf(\n                \"success\" to true,\n                \"data\" to response.body<Map<String, Any>>(),\n                \"systemTransactionId\" to response.body<Map<String, Any>>()[\"systemTransactionId\"],\n                \"providerTransactionId\" to response.body<Map<String, Any>>()[\"providerTransactionId\"]\n            )\n        )\n    } catch (e: Exception) {\n        call.respond(\n            HttpStatusCode.InternalServerError,\n            mapOf(\n                \"success\" to false,\n                \"error\" to \"Failed to complete order\",\n                \"message\" to e.message\n            )\n        )\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-5-handle-common-scenarios","__idx":15},"children":["Step 5: Handle common scenarios"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"shipping-address-validation","__idx":16},"children":["Shipping address validation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validate shipping addresses as customers change them in PayPal."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val paypalConfig = PayPalComponentConfig().apply {\n    userAction = UserAction.Continue.toString()\n    \n    onShippingAddressChange = { dataJson ->\n        Log.d(\"PayPal\", \"Shipping address changed: $dataJson\")\n        \n        try {\n            // Parse the JSON data to check shipping address\n            val jsonObject = JSONObject(dataJson)\n            val shippingAddress = jsonObject.optJSONObject(\"shippingAddress\")\n            \n            if (shippingAddress != null) {\n                val countryCode = shippingAddress.optString(\"countryCode\", \"\")\n                val state = shippingAddress.optString(\"state\", \"\")\n                val postalCode = shippingAddress.optString(\"postalCode\", \"\")\n                \n                // Validate country\n                val allowedCountries = listOf(\"US\", \"CA\")\n                if (!allowedCountries.contains(countryCode)) {\n                    return@onShippingAddressChange \"reject:COUNTRY_ERROR\"\n                }\n                \n                // Validate state for US addresses\n                if (countryCode == \"US\") {\n                    val restrictedStates = listOf(\"AK\", \"HI\") // Alaska, Hawaii\n                    if (restrictedStates.contains(state)) {\n                        return@onShippingAddressChange \"reject:STATE_ERROR\"\n                    }\n                }\n                \n                // Validate postal code format\n                if (countryCode == \"US\") {\n                    val postalCodeRegex = Regex(\"\"\"^\\d{5}(-\\d{4})?$\"\"\")\n                    if (!postalCodeRegex.matches(postalCode)) {\n                        return@onShippingAddressChange \"reject:POSTAL_CODE_ERROR\"\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(\"PayPal\", \"Error parsing shipping address\", e)\n        }\n        \n        // Return null to allow the change\n        null\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"review-screen-cancellation","__idx":17},"children":["Review screen cancellation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Handle when customers cancel from the review screen."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"fun handleCancelReview(navController: NavController) {\n    Log.d(\"PayPal\", \"Customer cancelled from review screen\")\n    \n    // Log analytics event\n    analytics.track(\"review_page_cancelled\", mapOf(\n        \"order_id\" to orderData.orderId,\n        \"stage\" to \"review\"\n    ))\n    \n    // Clear order data\n    viewModel.clearOrderData()\n    \n    // Return to checkout screen\n    navController.popBackStack()\n    \n    // Show message\n    Toast.makeText(context, \"Order cancelled. You can start a new checkout anytime.\", Toast.LENGTH_SHORT).show()\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-6-handle-errors","__idx":18},"children":["Step 6: Handle errors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement comprehensive error handling for PayPal continue flow."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val paypalConfig = PayPalComponentConfig().apply {\n    userAction = UserAction.Continue.toString()\n    \n    onError = { errorJson ->\n        Log.e(\"PayPal\", \"PayPal error: $errorJson\")\n        \n        // Parse error from JSON string\n        val error = parsePayPalError(errorJson)\n        \n        // Handle specific PayPal error types\n        val errorMessage = when (error.name) {\n            \"VALIDATION_ERROR\" -> \"Payment details validation failed. Please try again.\"\n            \"INSTRUMENT_DECLINED\" -> \"Your PayPal payment method was declined. Please try a different method.\"\n            \"PAYER_ACTION_REQUIRED\" -> \"Additional action required in PayPal. Please complete the payment process.\"\n            \"UNPROCESSABLE_ENTITY\" -> \"Payment cannot be processed. Please contact support.\"\n            else -> \"Payment failed. Please try again or contact support.\"\n        }\n        \n        showError(errorMessage)\n    }\n\n    onSuccess = { dataJson ->\n        try {\n            val orderData = parsePayPalResponse(dataJson)\n            navigateToReviewScreen(orderData)\n        } catch (e: Exception) {\n            Log.e(\"PayPal\", \"Failed to retrieve order details\", e)\n            \n            val errorMessage = when {\n                e is HttpException && e.code() == 404 -> \"Order not found. Please start a new checkout.\"\n                e is HttpException && e.code() == 422 -> \"Order details could not be retrieved. Please try again.\"\n                e is HttpException && e.code() >= 500 -> \"PayPal service temporarily unavailable. Please try again later.\"\n                else -> \"Unable to retrieve order details. Please try again.\"\n            }\n            \n            showError(errorMessage)\n        }\n    }\n}\n\n// Handle order submission errors\nsuspend fun handleSubmitOrder(orderData: OrderData) {\n    try {\n        val result = capturePayment(orderData)\n        // Handle success\n    } catch (e: Exception) {\n        Log.e(\"PayPal\", \"Payment capture error\", e)\n        \n        val errorMessage = when {\n            e is HttpException && e.code() == 422 -> \"Payment details could not be verified. Please try again.\"\n            e is HttpException && e.code() == 409 -> \"This payment has already been processed.\"\n            e is HttpException && e.code() >= 500 -> \"Payment system temporarily unavailable. Please try again later.\"\n            else -> \"Payment processing failed. Please try again.\"\n        }\n        \n        showError(errorMessage)\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"webhook-integration","__idx":19},"children":["Webhook integration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Unity sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PayPal transaction approved"]}," webhooks when PayPal orders are approved, providing complete payer details and shipping information."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"eventCategory\": \"transaction\",\n  \"eventDate\": \"2026-03-17T09:50:05.3525590Z\",\n  \"eventData\": {\n    \"state\": \"Approved\",\n    \"amounts\": {\n      \"transaction\": 10,\n      \"currencyCode\": \"USD\"\n    },\n    \"merchant\": \"merchant-1\",\n    \"site\": \"site-1\",\n    \"transactionMethod\": {\n      \"intent\": \"Create\",\n      \"fundingType\": \"Paypal\",\n      \"entryType\": \"Ecom\"\n    },\n    \"shippingAddress\": {\n      \"addressLine1\": \"123 Main St\",\n      \"postalCode\": \"95131\",\n      \"countryCode\": \"US\",\n      \"city\": \"San Francisco\",\n      \"county\": \"CA\"\n    },\n    \"merchantTransactionDate\": \"2026-03-06T02:53:12\",\n    \"merchantTransactionId\": \"99ECC73F-A9A9-44E2-9821-4F4797B3145D\",\n    \"systemTransactionId\": \"c3898060-431d-4829-bcde-049edcee70ae\",\n    \"providerTransactionId\": \"0DC51351HB1980105\",\n    \"fundingData\": {\n      \"paypal\": {\n        \"payeeEmailAddress\": \"merchant@example.com\",\n        \"paymentSource\": \"Paypal\"\n      }\n    },\n    \"payerDetails\": {\n      \"emailAddress\": \"john.doe@example.com\",\n      \"givenName\": \"John\",\n      \"surName\": \"Doe\",\n      \"phoneNumber\": \"4087098069\"\n    },\n    \"eventCode\": \"TXNPP_APP\"\n  },\n  \"eventOwner\": {\n    \"merchantGroup\": \"MerchantGroup\",\n    \"merchant\": \"merchant-1\",\n    \"site\": \"site-1\"\n  }\n}\n","lang":"json"},"children":[]},{"$$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":"Parameter"},"children":["Parameter"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eventCategory"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The category of the webhook event (e.g., \"transaction\")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eventDate"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The date and time when the event occurred, in ISO 8601 format."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eventCode"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The event code. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TXNPP_APP"]}," indicates PayPal order approved."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payerDetails.emailAddress"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's email address from PayPal."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payerDetails.givenName"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's first name from PayPal."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payerDetails.surName"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's last name from PayPal."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payerDetails.phoneNumber"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The customer's phone number from PayPal (if provided)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["shippingAddress"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Complete shipping address if customer selected one in PayPal."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["systemTransactionId"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Unity's internal transaction ID."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["providerTransactionId"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["PayPal's transaction ID."]}]}]}]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The payer details (email, name, phone) are also displayed in the Unity Portal's reporting system for PayPal transaction details."]}]}]},"headings":[{"value":"Continue flow","id":"continue-flow","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Payment flow","id":"payment-flow","depth":2},{"value":"Step 1: Submission","id":"step-1-submission","depth":3},{"value":"Step 2: Order creation","id":"step-2-order-creation","depth":3},{"value":"Step 3: PayPal approval","id":"step-3-paypal-approval","depth":3},{"value":"Step 4: Return to merchant","id":"step-4-return-to-merchant","depth":3},{"value":"Step 5: Order review","id":"step-5-order-review","depth":3},{"value":"Step 6: Payment capture","id":"step-6-payment-capture","depth":3},{"value":"Implementation","id":"implementation","depth":2},{"value":"Before you start","id":"before-you-start","depth":3},{"value":"Step 1: Configure your SDK","id":"step-1-configure-your-sdk","depth":3},{"value":"Step 2: Create the continue button","id":"step-2-create-the-continue-button","depth":3},{"value":"Step 3: Implement the review screen","id":"step-3-implement-the-review-screen","depth":3},{"value":"Step 4: Handle order submission","id":"step-4-handle-order-submission","depth":3},{"value":"Step 5: Handle common scenarios","id":"step-5-handle-common-scenarios","depth":3},{"value":"Shipping address validation","id":"shipping-address-validation","depth":4},{"value":"Review screen cancellation","id":"review-screen-cancellation","depth":4},{"value":"Step 6: Handle errors","id":"step-6-handle-errors","depth":3},{"value":"Webhook integration","id":"webhook-integration","depth":2}],"frontmatter":{"seo":{"title":"Continue flow"}},"lastModified":"2026-06-12T11:56:36.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/android/paypal/continue","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}