PayPal

Vaulted Payments

Vaulted payments with PayPal account will allow you to charge the account in the future without requiring your customer to be present during the transaction or re-authenticate with PayPal when they are present during the transaction.

Vaulting creates a PayPal pre-approved payment between you and the customer, displayed in the customer's account profile on PayPal.com.

Important

The SSL certificates for all Braintree SDKs are set to expire by June 30, 2025. This will impact existing versions of the SDK in published versions of your app. To reduce the impact, upgrade the Android SDK to version 4.45.0+ or version 5.0.0+ for the new SSL certifications.

If you do not decommission your app versions that include the older SDK versions or force upgrade your app with the updated certificates by the expiration date, 100% of your customer traffic will fail.

The Vault is used to process payments with our recurring billing feature, and is also used for non-recurring transactions so that your customers don't need to re-enter their information each time they make a purchase from you.

Vaulted payments with PayPal account allows you to charge the account in the future without requiring your customer’s presence during the transaction.

The vaulted payment flow lets the user:

  • Select or add shipping addresses in the PayPal account

  • Select or add funding instruments in the PayPal account

  • Two-factor authentication support (currently only for US, UK, CA, DE, AT, and AU)

pwpp_android_screens

Typical use cases for the vaulted payment flow:

  • Faster payments for repeat customers
  • Subscriptions
  • Recurring billing (e.g. automatic top-up or usage based charges)

Launch the flowAnchorIcon

  1. Kotlin
// Pass the user's email on the vault request
  val vaultRequest = PayPalVaultRequest(hasUserLocationConsent = true)
  vaultRequest.userAuthenticationEmail = "\<USER_EMAIL>"
  1. Groovy
dependencies {
    implementation 'com.braintreepayments.api:paypal:5.2.0'
}

Invoking the Vault flowAnchorIcon

InitializationAnchorIcon

Create a PayPalLauncher inside of your Activity's onCreate() . Then, create a PayPalClient with a Tokenization Key or Client Token and an appLinkReturnUrl that is used to return to your app from the PayPal payment flows.

  1. Kotlin
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
    )

Request a paymentAnchorIcon

After the user clicks the PayPal button, create a PayPalVaultRequest and call PayPalClient.createPaymentAuthRequest() and handle its result. If the result is Failure, handle the error. If the result is ReadyToLaunch, invoke PayPalLauncher.launch() and check the returned PayPalPendingRequest.

If Started is returned, the PayPal flow was launched and the Started.pendingRequestString needs to be persisted. If Failure was returned, handle the error.

  1. Kotlin
private fun onPayPalButtonClick() {
    val payPalVaultRequest = PayPalVaultRequest(
        hasUserLocationConsent = true,
        billingAgreementDescription = "Your agreement description"
    )
    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 */ }
        }
    }
}

Handle the payment resultAnchorIcon

Once the user completes or cancels the PayPal Vault flow the Intent needs to be handled. If your Activity's launch mode is >SINGLE_TOP, the Intent needs to be handled in onNewIntent(). For all other launch modes, handle the Intent in onResume().

Call PayPalLauncher.handleReturnToApp() and pass in the persisted pendingRequestString and Intent. Handle the returned PayPalPaymentAuthResult.

If NoResult is returned, the user has canceled the payment flow or returned to the app without completing the payPal flow. If Failure is returned, handle the error. If Success is returned, pass the Success object to PayPalClient.tokenize().

The PayPalClient.tokenize() function takes a PayPalTokenizeCallback which returns a PayPalResult. When the result is Cancel, the user canceled the flow. When the result is Failure, handle the error. When the result is Success, a nonce value is returned inside of PayPalAccountNonce.

You can query the PayPalAccountNonce result for specific customer information if needed.

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

Complete IntegrationAnchorIcon

  1. Kotlin
class MyActivity : 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"
        )
        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 */ }
            }
        }
    }
}

Collecting device dataAnchorIcon

Collecting device data from your customers is required when initiating non-recurring transactions from Vault records. Collecting and passing this data with transactions will help reduce decline rates.

DataCollector enables you to collect data about a customer's device and correlate it with a session identifier on your server.

App SwitchAnchorIcon

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. Groovy
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:

  1. Log into your Control Panel (e.g. Sandbox, or Production).
  2. Click on the gear icon in the top right corner. A drop-down menu will open.
  3. Click Account Settings from the drop-down menu.
  4. Scroll to the Payment Methods section.
  5. Next to PayPal, click the Options link. This will take you to your linked PayPal Account(s) page.
  6. 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.
  7. Click the + Add link on the top right of the page or scroll to the Specify Your Domain Names section.
  8. 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.
  9. Click the Add Domain Names button.
  10. 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.
  11. 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

Opt in to the App Switch flowAnchorIcon

Construct a PayPalVaultRequest or a PayPalCheckoutRequest with enablePayPalAppSwitch set to true and a userAuthenticationEmail included.

An example integration might look like this:

  1. Kotlin
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)
}

// vault flows
val request = PayPalVaultRequest(hasUserLocationConsent) 

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

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

fun onPayPalButtonClick() {
    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) { /* handle error */ }
                }
            }
            is PayPalPaymentAuthRequest.Failure -> { /* 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 -> // user returned to app without completing PayPal flow, handle accordingly
        }
    }   
}
    
fun completePayPalFlow(paymentAuthResult: PayPalPaymentAuthResult) {
    payPalClient.tokenize(paymentAuthResult) { result ->
        when(result) {
            is PayPalResult.Success -> { /* handle result.nonce */ }
            is PayPalResult.Failure -> { /* handle result.error */ }
            is PayPalResult.Cancel -> { /* handle user canceled */ }
        }          
    }
}

Note
In order to test App Switch flow in Sandbox refer to our Testing and Go Live section.

Shipping addressAnchorIcon

Shipping addresses may or may not be collected during the PayPal Vault flow. However, if you choose to collect shipping addresses yourself, it can be passed along with the server side Transaction.Sale call. Look at the Server-side page for more information.

Country and language supportAnchorIcon

PayPal is available to merchants in all countries that we support and to customers in 140+ countries.

Currency presentmentAnchorIcon

In the Vault flow itself, the transaction currency and amount are not displayed to the customer. It is up to you to display these details in your checkout flow somewhere (e.g. cart page, order review page, etc.). Our Server-Side guide outlines which currencies are supported for PayPal transactions.


Next Page: Recurring Payments

If you accept cookies, we’ll use them to improve and customize your experience and enable our partners to show you personalized PayPal ads when you visit other sites. Manage cookies and learn more