App Switch

The App Switch initiative will enable PayPal users who have a PayPal installed on their phone to complete payments on the PayPal app when it is available.

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. If the switch into the PayPal App cannot be completed, we will fall back to the default browser experience.

Get the SDKAnchorIcon

Add the following in your app-level build.gradle:

  1. Kotlin
dependencies {
    implementation 'com.braintreepayments.api:paypal:5.8.0'
}

In order to use the PayPal App Switch flow your application must be set up for App Links.

A buyer may uncheck the "Open supported links" setting on their Android device, preventing return to your app from the PayPal app via app links. You can provide a fallback deep link return URL scheme that will be used to return to your app from the PayPal app when app links are unavailable on the user's device. 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
    )

Before using this feature, you must register your App Link domain in the Braintree Control Panel:

  • Log into your Control Panel (e.g. Sandbox, or Production).
  • Click on the gear icon in the top right corner. A drop-down menu will open.
  • Click Account Settings from the drop-down menu.
  • Scroll to the Payment Methods section.
  • Next to PayPal, click the Options link. This will take you to your linked PayPal Account(s) page.
  • Click the View Domain Names button. This will take you to the PayPal Domain Names page.
    • Note: If you have a single PayPal account, it will be at the bottom of the page. If you have multiple PayPal accounts, it will be at the top right of the page.
  • Click the + Add link on the top right of the page or scroll to the Specify Your Domain Names section.
  • In the text box enter your list of domain names separated by commas.
    • Note: The value you enter must match your fully qualified domain name exactly – including the "www." if applicable.
  • Click the Add Domain Names button.
  • If the domain registration was successful for all the domain names listed in the text box, a banner will display the text "Successfully added domains". The registered domain names will be displayed in alphabetical order under the + Add button.
  • If the registration was not successful for any of the domain names listed in the text box, a banner will display a list of domain names that failed qualified domain name validation along with their reasons for rejection. Any domain names that were successfully registered will be displayed in alphabetical order under the + Add button.
    • Note: You can re-enter the rejected domain names in the text area with the corrections applied.

You will need to use the following PayPalClient constructor to set your App Link:

  1. Kotlin
val payPalClient =  PayPalClient(
  this, 
  "<#CLIENT_AUTHORIZATION#>",
  Uri.parse("https://demo-app.com/braintree-payments") // Merchant App Link
)

Invoking the PayPal App Switch flowAnchorIcon

Vaulted Payments with App SwitchAnchorIcon

An example integration might look like this:

  1. Kotlin
class MyActivity : AppCompatActivity() {

    private lateinit var payPalButton: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        payPalLauncher = PayPalLauncher()
    }

    // ONLY REQUIRED IF YOUR ACTIVITY LAUNCH MODE IS SINGLE_TOP
    override fun onNewIntent(intent: Intent) {
        handleReturnToAppFromBrowser(intent)
    }
        
    // ALL OTHER ACTIVITY LAUNCH MODES 
    override fun onResume() {
        handleReturnToAppFromBrowser(requireActivity().intent)
    }

    fun onPayPalButtonClick() {
        payPalButton.isEnabled = false

        // one time checkout flows
        val request = PayPalCheckoutRequest(amount, hasUserLocationConsent) 

        request.userAuthenticationEmail = "sally@gmail.com"
        request.enablePayPalAppSwitch = true

        payPalClient.createPaymentAuthRequest(this, request) { paymentAuthRequest ->
            when(paymentAuthRequest) {
                is PayPalPaymentAuthRequest.ReadyToLaunch -> {
                    val pendingRequest = payPalLauncher.launch(this@MyActivity, paymentAuthRequest)
                    when(pendingRequest) {
                        is (PayPalPendingRequest.Started) { /* store pending request */ }
                        is (PayPalPendingRequest.Failure) { 
                            payPalButton.isEnabled = true 
                            /* handle error */ 
                        }
                    }
                }
                is PayPalPaymentAuthRequest.Failure -> { 
                    payPalButton.isEnabled = true 
                    /* handle paymentAuthRequest.error */ 
                }
            }
        }
    }

    fun handleReturnToAppFromBrowser(intent: Intent) {
        // fetch stored PayPalPendingRequest.Started 
        fetchPendingRequestFromPersistentStorage()?.let {
            when (val paymentAuthResult = payPalLauncher.handleReturnToAppFromBrowser(it, intent)) {               
                is PayPalPaymentAuthResult.Success -> {
                    completePayPalFlow(paymentAuthResult)
                    // clear stored PayPalPendingRequest.Started
                }
                is PayPalPaymentAuthResult.NoResult -> {
                    payPalButton.isEnabled = true 
                    // user returned to app without completing PayPal flow, handle accordingly
                }
            }
        }   
    }
        
    fun completePayPalFlow(paymentAuthResult: PayPalPaymentAuthResult) {
        payPalButton.isEnabled = true 
        payPalClient.tokenize(paymentAuthResult) { result ->
            when(result) {
                is PayPalResult.Success -> { /* handle result.nonce */ }
                is PayPalResult.Failure -> { /* handle result.error */ }
                is PayPalResult.Cancel -> { /* handle user canceled */ }
            }          
        }
    }
}

One-time Payments with App SwitchAnchorIcon

An example integration might look like this:

class MyActivity : AppCompatActivity() {
    private lateinit var payPalButton: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        payPalLauncher = PayPalLauncher()
    }
    
    // ONLY REQUIRED IF YOUR ACTIVITY LAUNCH MODE IS SINGLE_TOP
    override fun onNewIntent(intent: Intent) {
        handleReturnToAppFromBrowser(intent)
    }
    
    // ALL OTHER ACTIVITY LAUNCH MODES
    override fun onResume() {
        handleReturnToAppFromBrowser(requireActivity().intent)
    }
    
    fun onPayPalButtonClick() {
        payPalButton.isEnabled = false
        
        // one time checkout flows
        val request = PayPalCheckoutRequest(amount, hasUserLocationConsent)
        request.userAuthenticationEmail = "sally@gmail.com"
        request.enablePayPalAppSwitch = true
        
        payPalClient.createPaymentAuthRequest(this, request) { paymentAuthRequest ->
            when(paymentAuthRequest) {
                is PayPalPaymentAuthRequest.ReadyToLaunch -> {
                    val pendingRequest = payPalLauncher.launch(this@MyActivity, paymentAuthRequest)
                    when(pendingRequest) {
                        is (PayPalPendingRequest.Started) { /* store pending request */ }
                        is (PayPalPendingRequest.Failure) {
                            payPalButton.isEnabled = true
                            /* handle error */
                        }
                    }
                }
                is PayPalPaymentAuthRequest.Failure -> {
                    payPalButton.isEnabled = true
                    /* handle paymentAuthRequest.error */
                }
            }
        }
    }
    
    fun handleReturnToAppFromBrowser(intent: Intent) {
        // fetch stored PayPalPendingRequest.Started
        fetchPendingRequestFromPersistentStorage()?.let {
            when (val paymentAuthResult = payPalLauncher.handleReturnToAppFromBrowser(it, intent)) {
                is PayPalPaymentAuthResult.Success -> {
                    completePayPalFlow(paymentAuthResult)
                    // clear stored PayPalPendingRequest.Started
                }
                is PayPalPaymentAuthResult.NoResult -> {
                    payPalButton.isEnabled = true
                    // user returned to app without completing PayPal flow, handle accordingly
                }
            }
        }
    }
    
    fun completePayPalFlow(paymentAuthResult: PayPalPaymentAuthResult) {
        payPalButton.isEnabled = true
        payPalClient.tokenize(paymentAuthResult) { result ->
            when(result) {
                is PayPalResult.Success -> { /* handle result.nonce */ }
                is PayPalResult.Failure -> { /* handle result.error */ }
                is PayPalResult.Cancel -> { /* handle user canceled */ }
            }
        }
    }
}

Complete ExamplesAnchorIcon

Complete Example - Vaulted Payments with App SwitchAnchorIcon

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"
        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 */ }
            }
        }
    }
}

Complete Example - One-time Payments with App SwitchAnchorIcon

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.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 */ }
            }
        }
    }
}

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