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.
Eligibility
- 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 integration
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:
- 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 flow
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()
andonCancel()
will continue to work.
Complete App Switch Sample for One-time Payments
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 Payments
// 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.