Overview

App Switch enables PayPal users who have a PayPal app installed on their phone to complete payment on the app when it is available.

This design requires a merchant to adopt a new integration pattern called "redirect flow" that supports the ability for a full-page client-side redirect within the browser either to App Switch or to redirect to PayPal in the same tab.

EligibilityAnchorIcon

  • Available for buyers and sellers in the United States only.
  • App switch is available with One-time payments and Vaulted payments in Pay Now flows only.
  • App Switch does not work with the following JavaScript SDK features:
    • onShippingChange
    • onShippingAddressChange
    • onShippingOptionsChange
  • App Switch doesn't work with iframes. Running the Javascript SDK in an iframe will block App Switch capabilities.

Client-Side integrationAnchorIcon

createOrderCallback has two new options to opt into the app switch flow: returnUrl and cancelUrl. Merchants can pass in the new options in their paypalCheckoutInstance.createPayment() call like this:

  1. javascript
// Add return and cancel URLs to createPayment() request options
createPaymentRequestOptions.returnUrl = 'example.com/return'
createPaymentRequestOptions.cancelUrl = 'example.com/cancel'

const button = paypal
  .Buttons({
    fundingSource: paypal.FUNDING.PAYPAL,

    // Sets up the transaction when a payment button is clicked
    appSwitchWhenAvailable: true, // Indicator to trigger app switch

    createOrder: createOrderCallback, // Set up in main the one-time payments example
    onApprove: onApproveCallback, // Set up in main the one-time payments example
    onError: onErrorCallback, // Set up in main the one-time payments example
  });

// If you support app switch, depending on the browser the buyer may end up in a new tab
// To trigger completion of the flow, execute the code below after re-instantiating buttons
if (button.hasReturned()) {
  button.resume();
} else {
  button.render("#paypal-button-container");
}

Resume flowAnchorIcon

The resume flow solves for some edge cases by helping a buyer resume a transaction completed in a different window or browser tab. However, this resume flow can lead to additional issues:

  • Client-side state loss: If a merchant relies on a client-side state, that state can be lost in a new browser or tab. For example, if a buyer is authenticated in a tab where they initially started a transaction, but they end up in a new browser, they won't be authenticated in the new browser. Prepare for this possibility by including enough of the original state in the redirect URL so that the buyer can complete the transaction in a new browser.
  • Open tab behavior: If a buyer is redirected to a new browser tab, the original tab remains open. The buyer can close the original tab after completing the transaction. If the buyer doesn't close the tab, the tab remains open in a loading state until it times out.
  • Client-side action calls: The resume flow won't work with client-side action calls. Replace client-side actions.* with server-side calls.
  • Callbacks: Client-side callbacks such as onApprove() and onCancel() will continue to work.

Complete App Switch Sample for One-time PaymentsAnchorIcon

Here's a complete example integrating App Switch with the one-time payments flow:

// Create a PayPal Checkout component.
braintree.paypalCheckout.create({
  client: clientInstance
}, function (paypalCheckoutErr, paypalCheckoutInstance) {
  // Base PayPal SDK script options
  var loadPayPalSDKOptions = {
    currency: 'USD', // Must match the currency passed in with createPayment
    intent: 'capture', // Must match the intent passed in with createPayment
    commit: true // Required for Pay Now flow with App Switch
  }

  paypalCheckoutInstance.loadPayPalSDK(loadPayPalSDKOptions, function () {
    const button = paypal.Buttons({
      fundingSource: paypal.FUNDING.PAYPAL,
      appSwitchWhenAvailable: true, // Indicator to trigger app switch
      createOrder: function () {
        // Base payment request options for one-time payments
        var createPaymentRequestOptions = {
          flow: 'checkout', // Required
          amount: 10.00, // Required
          currency: 'USD', // Required, must match the currency passed in with loadPayPalSDK
          intent: 'capture', // Must match the intent passed in with loadPayPalSDK
          // App Switch specific options
          returnUrl: 'example.com/return',
          cancelUrl: 'example.com/cancel'
        };
        
        return paypalCheckoutInstance.createPayment(createPaymentRequestOptions);
      },
      onApprove: function (data, actions) {
        return paypalCheckoutInstance.tokenizePayment(data, function (err, payload) {
          // Submit 'payload.nonce' to your server
        });
      },
      onCancel: function (data) {
        console.log('PayPal payment cancelled', JSON.stringify(data, 0, 2));
      },
      onError: function (err) {
        console.error('PayPal error', err);
      }
    });

    // If you support app switch, depending on the browser the buyer may end up in a new tab
    // To trigger completion of the flow, execute the code below after re-instantiating buttons
    if (button.hasReturned()) {
      button.resume();
    } else {
      button.render('#paypal-button-container').then(function () {
        // The PayPal button will be rendered in an html element with the ID
        // 'paypal-button-container'. This function will be called when the PayPal button
        // is set up and ready to be used
      });
    }
  });
});

Complete App Switch Sample for Vaulted PaymentsAnchorIcon

// Create a client.
braintree.client.create({
  authorization: CLIENT_AUTHORIZATION
}, function (clientErr, clientInstance) {
  // Stop if there was a problem creating the client.
  // This could happen if there is a network error or if the authorization
  // is invalid.
  if (clientErr) {
    console.error('Error creating client:', clientErr);
    return;
  }

  // Create a PayPal Checkout component.
  braintree.paypalCheckout.create({
    client: clientInstance
  }, function (paypalCheckoutErr, paypalCheckoutInstance) {
    if (paypalCheckoutErr) {
      console.error('Error creating PayPal Checkout:', paypalCheckoutErr);
      return;
    }

    paypalCheckoutInstance.loadPayPalSDK({
      vault: true
    }, function () {
      const buttons = paypal
        .Buttons({
          fundingSource: paypal.FUNDING.PAYPAL,
          // Sets up the transaction when a payment button is clicked
          appSwitchWhenAvailable: true, // Need an indicator to trigger app switch
          createBillingAgreement: function () {
            return paypalCheckoutInstance.createPayment({
              flow: 'vault', // Required
              // App Switch specific options
              returnUrl: 'example.com/return',
              cancelUrl: 'example.com/cancel',
              // The following are optional params
              billingAgreementDescription: 'Your agreement description',
              enableShippingAddress: true,
              shippingAddressEditable: false,
              shippingAddressOverride: {
                recipientName: 'Scruff McGruff',
                line1: '1234 Main St.',
                line2: 'Unit 1',
                city: 'Chicago',
                countryCode: 'US',
                postalCode: '60652',
                state: 'IL',
                phone: '123.456.7890'
              }
            });
          },
          onApprove: function (data, actions) {
            return paypalCheckoutInstance.tokenizePayment(data, function (err, payload) {
              if (err) {
                console.error('Tokenization error:', err);
                return;
              }
              // Submit 'payload.nonce' to your server
              console.log('PayPal nonce for vaulting:', payload.nonce);
            });
          },
          onCancel: function (data) {
            console.log('PayPal payment canceled', JSON.stringify(data, 0, 2));
          },
          onError: function (error) {
            // Do something with the error from the SDK
            console.error('PayPal error:', error);
          },
        });

      // If you support app switch, depending on the browser the buyer may end up in a new tab
      // To trigger completion of the flow, execute the code below after re-instantiating buttons
      if (buttons.hasReturned()) {
        buttons.resume();
      } else {
        buttons.render("#paypal-button-container").then(function () {
          // The PayPal button will be rendered in an html element with the ID
          // 'paypal-button-container'. This function will be called when the PayPal button
          // is set up and ready to be used
        });
      }
    });
  });
});

Use the paypalCheckoutInstance in the onApprove function of the JavaScript SDK setup method to tokenize the PayPal account. After the customer completes the consent flow and the PayPal pop-up closes, successful tokenization will return a payment method nonce.

Send the nonce to your server and use a Braintree server SDK to call Transaction.sale to create a transaction.

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