App Switch

App Switch enables buyers with the PayPal app installed to start checkout or save a payment method in a merchant app or website and seamlessly complete the flow in the PayPal app. This provides a low-friction authentication experience using native PayPal app login methods such as biometrics or passkeys.

When App Switch is enabled, the Braintree Android SDK attempts to switch to the PayPal app after PayPalLauncher().launch is called, provided the PayPal app is installed, and the buyer meets eligibility requirements. If App Switch is unavailable, the SDK automatically falls back to the PayPal web experience.

This guide is for merchants who are using the Braintree Android SDK. App Switch is eligible for the following checkout options, all with the Pay Now consumer experience:

One-time Checkout and Save Payment Method with Purchase also support PayPal Pay Later — set shouldOfferPayLater = true on the request to prioritize installment options on the PayPal screen.

Understand the buyer flowAnchorIcon

The following steps describe the buyer experience when App Switch is available and used during checkout or when saving a payment method.

  1. The buyer initiates PayPal checkout or saves a payment method from the merchant's native mobile app.
  2. If the PayPal app is installed and the buyer is eligible, the SDK switches context from the merchant app to the PayPal app.
  3. The buyer is authenticated in the PayPal app using low-friction login methods such as biometrics, passkeys, or an existing PayPal app session.
  4. The buyer reviews the transaction or save-payment details and approves the action in the PayPal app.
  5. The buyer is redirected back to the merchant app.
  6. The merchant app completes the transaction or successfully saves PayPal as a payment method.

One-time payments with Pay NowAnchorIcon

One-time,payments,with,Pay,Now

Save Payment Method without PurchaseAnchorIcon

Save,Payment,Method,without,Purchase

Before you beginAnchorIcon

Integrate App SwitchAnchorIcon

Enable App Switch on the PayPal request and supply the buyer's email and phone number when available. When enablePayPalAppSwitch is true, the Braintree Android SDK attempts to switch to the PayPal app (after calling PayPalLauncher().launch(...)) if the PayPal app is installed and the buyer is eligible. Otherwise, it falls back to the web flow.

  1. Kotlin
request.userAuthenticationEmail = "[email protected]"
request.userPhoneNumber = PayPalPhoneNumber("1", "2223334444")
request.enablePayPalAppSwitch = true

Code SampleAnchorIcon

Each sample uses the Pay Now consumer experience (userAction = PayPalPaymentUserAction.USER_ACTION_PAY_NOW). For One-time Checkout and Save Payment Method with Purchase, the request includes shouldOfferPayLater with an inline comment showing how to prioritize PayPal Pay Later installment options on the PayPal screen.

One-time PaymentsAnchorIcon

  1. Kotlin
// One-time Checkout — Pay Now.
// USER_ACTION_PAY_NOW presents the "Pay Now" experience: the buyer commits to the payment
// in the PayPal app, and you present the final order status when they return.

val request = PayPalCheckoutRequest(
    hasUserLocationConsent = true,
    amount = "59.00"
).apply {
    enablePayPalAppSwitch = true
    userAuthenticationEmail = "[email protected]"  // Strongly recommended — improves App Switch eligibility
    userAction = PayPalPaymentUserAction.USER_ACTION_PAY_NOW
    shouldOfferPayLater = false  // Set to true to prioritize PayPal Pay Later installment options on the PayPal screen
}

// Use the official Braintree PayPal launcher to present the PayPal button flow
payPalClient.createPaymentAuthRequest(this, request) { authRequest ->
    when (authRequest) {
        is PayPalPaymentAuthRequest.ReadyToLaunch -> {
            pendingRequest = payPalLauncher.launch(this, authRequest)
        }
        is PayPalPaymentAuthRequest.Failure -> handleError(authRequest.error)
    }
}

// In your tokenize callback:
// PayPalResult.Success(nonce) -> createTransaction(nonce.string, "59.00")

Saved PaymentsAnchorIcon

  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 = "[email protected]"
        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 */ }
            }
        }
    }
}

Save Payment Method with PurchaseAnchorIcon

The Save Payment Method with Purchase flow charges the buyer and saves their PayPal account as a payment method for future use — in a single checkout step. The buyer approves both the payment and the save consent together in the PayPal app.

  1. Kotlin
// Save Payment Method with Purchase — the buyer pays and saves their PayPal account in one step.
// shouldRequestBillingAgreement = true — buyer consents to save their PayPal account for future use.
// USER_ACTION_PAY_NOW — the buyer commits to the payment in the PayPal app (no review step on your side).

val request = PayPalCheckoutRequest(
    hasUserLocationConsent = true,
    amount = "64.00"
).apply {
    enablePayPalAppSwitch = true
    userAuthenticationEmail = "[email protected]"  // Strongly recommended — improves App Switch eligibility
    userAction = PayPalPaymentUserAction.USER_ACTION_PAY_NOW
    shouldOfferPayLater = false  // Set to true to prioritize PayPal Pay Later installment options on the PayPal screen
    shouldRequestBillingAgreement = true  // Buyer consents to save their PayPal account
    billingAgreementDescription = "Save your PayPal for future purchases"
}

// Use the official Braintree PayPal launcher to present the PayPal button flow
payPalClient.createPaymentAuthRequest(this, request) { authRequest ->
    when (authRequest) {
        is PayPalPaymentAuthRequest.ReadyToLaunch -> {
            pendingRequest = payPalLauncher.launch(this, authRequest)
        }
        is PayPalPaymentAuthRequest.Failure -> handleError(authRequest.error)
    }
}

// In your tokenize callback:
// PayPalResult.Success(nonce) -> createTransactionAndSave(nonce.string, "64.00")
// Server-side: gateway.transaction.sale with storeInVaultOnSuccess: true
// Returns transaction ID + a reusable payment method token for future charges

On your server, call gateway.transaction.sale with storeInVaultOnSuccess: true. The response includes both the transaction ID and a reusable payment method token for future charges.

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:

  • 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.

2. 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

Buyers' email and phone numbers often result in higher overall conversion.

Phone number formatting requirementsAnchorIcon

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

3. 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.

You may optionally implement a timeout (for example, 10 seconds) for the loading indicator in case processing takes longer than expected. 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
    )

If your App Link URL contains a path (for example, https://merchant-app.com/paypal), you must register that path in your AndroidManifest.xml intent-filter using the pathPrefix attribute.

PayPal uses your App Link domain and path to return buyers to your app after completing payment. This includes both App Switch sessions and non-App Switch sessions (OneTouch, long-lived sessions). If the path prefix is not registered, your app will not handle the return URL for non-App Switch sessions, and those buyers will see a failed checkout.

Add the pathPrefix to your intent-filter in AndroidManifest.xml:

  1. XML
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" />
    <data android:host="merchant-app.com" />
    <!-- Required if your App Link URL contains a path -->
    <data android:pathPrefix="/your-app-switch-path" />
</intent-filter>

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