App Switch

App Switch allows buyers with the PayPal app installed to begin their transactions on a merchant's app or website and then seamlessly navigate to the PayPal app to complete their purchase or vault PayPal as a payment method. This feature is enabled through strong multi-factor authentication, allowing buyers to log in using biometrics or a passkey, rather than entering a password or one-time password (OTP).

In this experience the SDK will attempt to switch to the PayPal App after calling PayPalLauncher().launch if the PayPal App is installed and the user meets eligibility requirements. In cases where App Switch isn't available, the user continues with the existing PayPal web flow.

This guide is for merchants who are using the Braintree Android SDK with the following flows:

  • Vaulted Payments flow: Vaulting without Purchase
  • One-time Payments flow: One Time “Pay Now”
  • Buyer FlowAnchorIcon

    Native App to PayPal AppAnchorIcon

    The buyer initiates PayPal checkout within your native mobile application and is seamlessly redirected to the PayPal consumer app to review and approve the transaction.

    1. The buyer selects the PayPal button from your app.
    2. The PayPal app opens, and the buyer is authenticated using low-friction app login mechanisms such as a biometric login, App Long Lived Session, or passkey.
    3. The buyer reviews details and approves the Vault or transaction in the PayPal app.
    4. The buyer is redirected back to your app.
    5. Vault PayPal as a payment method, or complete the transaction

    PrerequisitesAnchorIcon

    Invoking the PayPal App Switch flowAnchorIcon

    Set the enablePayPalAppSwitch param to true on the request (PayPalVaultRequest or PayPalCheckoutRequest), and include user’s email address and phone number when available:

    1. Kotlin
    request.userAuthenticationEmail = "sally@gmail.com"
    request.userPhoneNumber = PayPalPhoneNumber("1", "2223334444")
    request.enablePayPalAppSwitch = true

    Vaulted Payments with App SwitchAnchorIcon

    An example integration might look like this:

    1. Kotlin
    class VaultedPaymentActivity : AppCompatActivity() {
        private lateinit var payPalLauncher: PayPalLauncher
        private lateinit var payPalClient: PayPalClient
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            
            // PayPalLauncher must be initialized in onCreate
            payPalLauncher = PayPalLauncher()
            
            // can initialize the PayPalClient outside of onCreate if desired
            payPalClient = PayPalClient(
                context = this,
                authorization = "[TOKENIZATION_KEY or CLIENT_TOKEN]",
                appLinkReturnUrl = Uri.parse("https://merchant-app.com"), // Merchant App Link
                "com.merchant.app.deeplink.braintree" // Fallback deep link return url scheme
            )
        }
        
        // ONLY REQUIRED IF YOUR ACTIVITY LAUNCH MODE IS SINGLE_TOP
        override fun onNewIntent(intent: Intent) {
            super.onNewIntent(intent)
            handleReturnToApp(intent)
        }
        
        // ALL OTHER ACTIVITY LAUNCH MODES
        override fun onResume() {
            super.onResume()
            handleReturnToApp(intent)
        }
        
        private fun handleReturnToApp(intent: Intent) {
            // fetch stored PayPalPendingRequest.Success
            val pendingRequest: String = fetchPendingRequestFromPersistantStore()
            when (val paymentAuthResult = payPalLauncher.handleReturnToApp(
                pendingRequest = PayPalPendingRequest.Started(pendingRequest),
                intent = intent
            )) {
                is PayPalPaymentAuthResult.Success -> {
                    completePayPalFlow(paymentAuthResult)
                    // clear stored PayPalPendingRequest.Success
                }
                is PayPalPaymentAuthResult.NoResult -> {
                    // user returned to app without completing PayPal flow, handle accordingly
                }
                is PayPalPaymentAuthResult.Failure -> {
                    // handle error case
                }
            }
        }
        
        private fun completePayPalFlow(paymentAuthResult: PayPalPaymentAuthResult.Success) {
            payPalClient.tokenize(paymentAuthResult) { result ->
                when (result) {
                    is PayPalResult.Success -> { /* handle result.nonce */ }
                    is PayPalResult.Failure -> { /* handle result.error */ }
                    is PayPalResult.Cancel -> { /* handle user canceled */ }
                }
            }
        }
        
        private fun onPayPalButtonClick() {
            val payPalVaultRequest = PayPalVaultRequest(
                hasUserLocationConsent = true,
                billingAgreementDescription = "Your agreement description"
            )
            payPalVaultRequest.userAuthenticationEmail = "sally@gmail.com"
            request.userPhoneNumber = PayPalPhoneNumber("1", "2223334444")
            payPalVaultRequest.enablePayPalAppSwitch = true
            
            payPalClient.createPaymentAuthRequest(this, payPalVaultRequest) { paymentAuthRequest ->
                when (paymentAuthRequest) {
                    is PayPalPaymentAuthRequest.ReadyToLaunch -> {
                        val pendingRequest = payPalLauncher.launch(this, paymentAuthRequest)
                        when (pendingRequest) {
                            is PayPalPendingRequest.Started -> {/* store pending request */ }
                            is PayPalPendingRequest.Failure -> { /* handle error */ }
                        }
                    }
                    is PayPalPaymentAuthRequest.Failure -> { /* handle paymentAuthRequest.error */ }
                }
            }
        }
    }

    One-time Payments with App SwitchAnchorIcon

    1. Kotlin
    class CheckoutActivity : AppCompatActivity() {
        private lateinit var payPalLauncher: PayPalLauncher
        private lateinit var payPalClient: PayPalClient
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            
            // PayPalLauncher must be initialized in onCreate
            payPalLauncher = PayPalLauncher()
            
            // can initialize the PayPalClient outside of onCreate if desired
            payPalClient = PayPalClient(
                context = this,
                authorization = "[TOKENIZATION_KEY or CLIENT_TOKEN]",
                appLinkReturnUrl = Uri.parse("https://merchant-app.com"), // Merchant App Link
                "com.merchant.app.deeplink.braintree" // Fallback deep link return url scheme
            )
        }
        
        // ONLY REQUIRED IF YOUR ACTIVITY LAUNCH MODE IS SINGLE_TOP
        override fun onNewIntent(intent: Intent) {
            super.onNewIntent(intent)
            handleReturnToApp(intent)
        }
        
        // ALL OTHER ACTIVITY LAUNCH MODES
        override fun onResume() {
            super.onResume()
            handleReturnToApp(intent)
        }
        
        private fun handleReturnToApp(intent: Intent) {
            // fetch stored PayPalPendingRequest.Success
            val pendingRequest: String = fetchPendingRequestFromPersistantStore()
            when (val paymentAuthResult = payPalLauncher.handleReturnToApp(
                pendingRequest = PayPalPendingRequest.Started(pendingRequest),
                intent = intent
            )) {
                is PayPalPaymentAuthResult.Success -> {
                    completePayPalFlow(paymentAuthResult)
                    // clear stored PayPalPendingRequest.Success
                    // clear the intent data
                    intent.data = null
                }
                is PayPalPaymentAuthResult.NoResult -> {
                    // user returned to app without completing PayPal flow, handle accordingly
                }
                is PayPalPaymentAuthResult.Failure -> {
                    // handle error case
                }
            }
        }
        
        private fun completePayPalFlow(paymentAuthResult: PayPalPaymentAuthResult.Success) {
            payPalClient.tokenize(paymentAuthResult) { result ->
                when (result) {
                    is PayPalResult.Success -> { /* handle result.nonce */ }
                    is PayPalResult.Failure -> { /* handle result.error */ }
                    is PayPalResult.Cancel -> { /* handle user canceled */ }
                }
            }
        }
        
        private fun onPayPalButtonClick() {
            val request = PayPalCheckoutRequest(
                amount = "12.34",
                hasUserLocationConsent = true
            )
            request.userAuthenticationEmail = "sally@gmail.com"
            request.userPhoneNumber = PayPalPhoneNumber("1", "2223334444")
            request.enablePayPalAppSwitch = true
            
            payPalClient.createPaymentAuthRequest(this, request) { paymentAuthRequest ->
                when (paymentAuthRequest) {
                    is PayPalPaymentAuthRequest.ReadyToLaunch -> {
                        val pendingRequest = payPalLauncher.launch(this, paymentAuthRequest)
                        when (pendingRequest) {
                            is PayPalPendingRequest.Started -> {/* store pending request */ }
                            is PayPalPendingRequest.Failure -> { /* handle error */ }
                        }
                    }
                    is PayPalPaymentAuthRequest.Failure -> { /* handle paymentAuthRequest.error */ }
                }
            }
        }
    }

Best PracticesAnchorIcon

These best practices are crucial for ensuring an optimal user experience and improved conversion rates.

1. Show a loading indicator after PayPal button clickAnchorIcon

    When the user taps the PayPal button:
  • Immediately turn off the payment button to prevent duplicate submissions.
  • Display a loading indicator while the network request is in progress.
  • The loading indicator not only protects against accidental double-clicks but also reassures the user that processing is underway.

Provide buyer email and phone numberAnchorIcon

Although optional, passing the buyer's email and phone number whenever available is strongly recommended.
Providing this data helps PayPal to:
  • Personalize the buyer experience
  • Improve risk assessment
  • Optimize approval rates
This often results in higher overall conversion. Note: When passing a phone number, ensure it is sanitized into its canonical format:
  • Country code should be numeric only (no “+” prefix).
  • National number must include digits only—remove spaces, hyphens, parentheses, and other formatting characters.
    • Do not include the country code in the national number.
    • Do not include leading zeros that are not part of the actual national number.
  • Example: request.userPhoneNumber = PayPalPhoneNumber("1", "2223334444")

Handling return to your appAnchorIcon

When the buyer returns to your app (either automatically or manually):
  • Remove the loading indicator when your app returns to the foreground.
  • This allows the user to continue where they left off, especially when they return without approving or canceling the transaction (for example, by backgrounding the PayPal app).
  • When the SDK callback returns a nonce (indicating the buyer approved the transaction):
    • Show the loading indicator again.
    • Turn off the PayPal button.
    • Continue processing until the transaction completes.
This ensures UI consistency and prevents unexpected user interactions during the payment finalization process. A buyer may uncheck the "Open supported links" setting on their Android device, which prevents them from returning to your app from the PayPal app through app links. You can provide a fallback deep link return URL scheme that will be used to complete the session. The regular Chrome Custom Tab flow will be displayed when the deep link fallback return URL is used, and App Switch will not occur in this scenario. Follow these steps to configure the deep link return URL scheme in your AndroidManifest.xml and pass the deep link return URL in your PayPalClient constructor.
  1. Kotlin
payPalClient = PayPalClient(
        context = this,
        authorization = "[TOKENIZATION_KEY or CLIENT_TOKEN]",
        appLinkReturnUrl = Uri.parse("https://merchant-app.com"), // Merchant App Link
        "com.merchant.app.deeplink.braintree" // Fallback deep link return url scheme
    )

For detailed testing procedures and production deployment guidance, see the Testing and Go Live section.