Client-Side Implementation
This guide covers the client-side JavaScript setup for adding a PayPal button to your checkout, from basic configuration through the PayPal JavaScript SDK v6 integration patterns for one-time, vault, recurring, and other payment flows.
The examples in this guide require version 3.90.0 or higher of the Braintree JavaScript SDK.
If you use the Braintree JavaScript SDK with the deprecated PayPal checkout.js library, review this migration guide to upgrade your integration.
Prerequisites
Before you can add a PayPal button, make sure you have a few prerequisites out of the way:
- Create, verify, and link your PayPal account in the Braintree Control Panel
- Generate a client token on your server
- See Hello, Server and Hello, Client for a walkthrough of creating and using a client token
- You will use the client token when you initialize your components
Browser support
Learn more about browser support for v3 of our JavaScript SDK.
Complete basic configuration
If you are using script tags to load files, be sure to at least include:
- HTML
<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.146.0/js/client.min.js"></script>
<!-- Load the PayPal Checkout component. -->
<script src="https://js.braintreegateway.com/web/3.146.0/js/paypal-checkout.min.js"></script>Regardless of which method you use to load files, create a div element where your PayPal button will appear.
- HTML
<div id="paypal-button"></div>Initialize components
Integrating PayPal payments requires a client. After you've created a client, pass it to the PayPal Checkout component to accept your payments.
- Async/Await
// Create a client.
async function setupPayPalCheckout() {
try {
const clientInstance = await braintree.client.create({
authorization: CLIENT-AUTHORIZATION
});
// Create a PayPal Checkout component.
const paypalCheckoutInstance = await braintree.paypalCheckout.create({
client: clientInstance
});
// Load the PayPal JS SDK (see Load the PayPal JS SDK section)
} catch (err) {
// Handle component creation error
}
}The console logs a component creation error if braintree.client.create or braintree.paypalCheckout.create fails.
Load the PayPal JS v6 SDK
We now support integrating the PayPal JavaScript SDK v6 through the Braintree JavaScript SDK v3 so merchants can add PayPal checkout with a single, high-level integration.
Prerequisites for the v6 integration
- Active Braintree account
- PayPal REST app with a client ID (sandbox or live) for the merchant or platform
- HTTPS web origin where you load the Braintree Web SDK and host your checkout
- A server environment where you already create and capture orders through Braintree's backend APIs (recommended pattern)
Minimum SDK versions
| SDK | Version |
|---|---|
| PayPal JavaScript Web SDK | v6 |
@paypal/paypal-js (npm) | 9.3.0 |
Complete common setup
Note: The PayPal JavaScript SDK requires a client token, not a tokenization key.
Load the Braintree client SDK and the PayPal Checkout v6 JavaScript library. These scripts are required for all PayPal v6 integrations.
- Load
<script src="https://js.braintreegateway.com/web/4.0.0-alpha.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/4.0.0-alpha.0/js/paypal-checkout-v6.min.js"></script>Automatic environment detection
The Braintree PayPal Checkout v6 wrapper automatically loads the correct PayPal SDK based on your Braintree environment configuration:
- Live environment: Loads the PayPal SDK from
paypal.com - Sandbox environment: Loads the PayPal SDK from
sandbox.paypal.com
This happens automatically when you call paypal.loadPayPalSDK(). No additional configuration is required. The environment is determined by the client token or authorization you provide when creating the Braintree client instance.
Set up a one-time payment
Basic checkout for a single purchase.
Define the PayPal trigger button that the integration will use to display transaction status or messages.
- HTML
<paypal-button type="pay" id="paypal-one-time-payment-button"></paypal-button>Initialize through a Braintree client token and create a PayPal Checkout v6 instance that will manage the PayPal JavaScript SDK and checkout flow.
- Async/Await
async function setupPayPalCheckoutV6() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createOneTimePaymentSession({
amount: "10.00",
currency: "USD",
intent: "capture",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
},
});
document
.getElementById("paypal-one-time-payment-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the nonce after the buyer approves the payment.
Set up the vault flow (save a PayPal account)
Save a PayPal account for future transactions without charging.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Initialize the Braintree client and PayPal Checkout v6, load the PayPal SDK, and start a billing agreement session that vaults the buyer's PayPal account through createBillingAgreementSession and tokenizePayment.
- Async/Await
async function setupVaultFlow() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createBillingAgreementSession({
billingAgreementDescription: "Save for future payments",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
billingToken: data.billingToken,
});
console.log("Vaulted nonce:", payload.nonce);
},
});
document
.getElementById("paypal-vault-button")
.addEventListener("click", function() {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the vaulted nonce after the buyer approves the agreement.
Set up a recurring plan
Fixed repeating payments on a schedule, like a monthly subscription.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Create a PayPal billing agreement for a recurring payment type using planType: 'RECURRING', have the buyer approve the agreement, then tokenize the billing agreement to charge the customer on a recurring schedule.
- Async/Await
async function setupRecurringPlan() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createBillingAgreementSession({
billingAgreementDescription: "Monthly recurring subscription",
planType: "RECURRING",
amount: "29.99",
currency: "USD",
planMetadata: {
billingCycles: [
{
billingFrequency: "1",
billingFrequencyUnit: "MONTH",
numberOfExecutions: "12",
sequence: "1",
startDate: new Date(Date.now() + 86400000)
.toISOString()
.split("T")[0],
trial: false,
pricingScheme: {
pricingModel: "VARIABLE",
price: "29.99",
},
},
],
currencyIsoCode: "USD",
name: "Monthly Recurring Plan",
productDescription: "Monthly recurring subscription service",
productQuantity: "1.0",
productPrice: "29.99",
totalAmount: "29.99",
},
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
console.log("Recurring nonce:", payload.nonce);
},
});
document
.getElementById("paypal-recurring-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the recurring nonce after the buyer approves the agreement.
Set up a subscription plan
Similar to recurring but specifically for subscriptions with configurable billing cycles.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Create a PayPal subscription flow using planType: 'SUBSCRIPTION' where the buyer clicks a Subscribe button. Manage and bill the customer on an ongoing schedule from your backend.
- Async/Await
async function setupSubscriptionPlan() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createBillingAgreementSession({
billingAgreementDescription: "Monthly subscription for premium service",
planType: "SUBSCRIPTION",
amount: "233.00",
currency: "USD",
planMetadata: {
billingCycles: [
{
billingFrequency: "1",
billingFrequencyUnit: "MONTH",
numberOfExecutions: "3",
sequence: "1",
startDate: new Date(Date.now() + 86400000)
.toISOString()
.split("T")[0],
trial: false,
pricingScheme: {
pricingModel: "FIXED",
price: "200.00",
},
},
],
currencyIsoCode: "USD",
name: "Premium Subscription",
productDescription: "Premium Service Plan",
productQuantity: "1.0",
oneTimeFeeAmount: "10",
shippingAmount: "3.0",
productPrice: "200",
taxAmount: "20",
totalAmount: "233.00",
},
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
console.log("Subscription nonce:", payload.nonce);
},
});
document
.getElementById("paypal-subscription-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the subscription nonce after the buyer approves the agreement.
Set up an unscheduled plan
Variable or on-demand payments, like pay-as-you-go services.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Create a billing agreement session with planType: 'UNSCHEDULED' for variable or on-demand charges, then tokenize the returned billing token so you can bill the customer whenever usage occurs.
- Async/Await
async function setupUnscheduledPlan() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createBillingAgreementSession({
billingAgreementDescription: "Pay-as-you-go service",
planType: "UNSCHEDULED",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({ billingToken: data.billingToken });
console.log("Unscheduled nonce:", payload.nonce);
},
});
document
.getElementById("paypal-unscheduled-plan-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the unscheduled nonce after the buyer approves the agreement.
Set up checkout with vault
Charge a customer and save their PayPal account in a single flow.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Initialize the Braintree client and PayPal Checkout v6 instance. Create a checkout session that both processes the payment and vaults the buyer's PayPal account for future use, so merchants can charge the customer again without requiring re-approval.
- Async/Await
async function setupCheckoutWithVault() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createCheckoutWithVaultSession({
amount: "10.00",
currency: "USD",
intent: "capture",
billingAgreementDetails: {
description: "Monthly subscription to Totally Real Products!",
},
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
orderID: data.orderId,
payerID: data.payerId,
});
console.log("Checkout with vault nonce:", payload.nonce);
},
});
document
.getElementById("paypal-checkout-with-vault-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the checkout-with-vault nonce after the buyer approves the payment.
Set up checkout with a vaulted payment method
Starts a vault-initiated checkout flow using a previously vaulted payment method token. This flow supports one-click repeat purchases without the PayPal SDK buttons.
Initialize the Braintree client and PayPal Checkout v6 instance. Create a checkout session that accepts a vaultInitiatedCheckoutPaymentMethodToken and both retrieves the vaulted payment method and processes the payment.
- Async/Await
async function setupVaultInitiatedCheckout() {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
try {
const payload = await paypal.startVaultInitiatedCheckout({
vaultInitiatedCheckoutPaymentMethodToken: 'pm_abc123_vaulted_paypal_token',
amount: '10.00',
currency: 'USD',
intent: 'capture'
});
// Submit payload.nonce to your server
console.log('Payment nonce:', payload.nonce);
} catch (err) {
if (err.code === 'PAYPAL_CHECKOUT_V6_VIC_CANCELED') {
console.log('Customer closed the popup');
}
}
}The browser console logs the payment nonce after the payment succeeds. A caught error with code PAYPAL_CHECKOUT_V6_VIC_CANCELED indicates the customer closed the popup.
Focus the checkout window
Focuses the PayPal window if it was opened using startVaultInitiatedCheckout.
- Javascript
paypal.focusVaultInitiatedCheckoutWindow();Close the checkout window
Closes the PayPal window if it was opened using startVaultInitiatedCheckout.
- Javascript
paypal.closeVaultInitiatedCheckoutWindow();Add line items and shipping options
Itemized checkout with shipping and tax breakdown.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Create a one-time payment session that includes detailed line items, including a discount, and multiple shipping options, so the PayPal checkout shows a full price breakdown before the buyer approves.
- Async/Await
async function setupLineItemsCheckout() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createOneTimePaymentSession({
amount: "40.00",
currency: "USD",
intent: "capture",
lineItems: [
{ quantity: "2", unitAmount: "15.00", name: "Widget", kind: "debit" },
{ quantity: "1", unitAmount: "10.00", name: "Gadget", kind: "debit" },
{ quantity: "1", unitAmount: "5.00", name: "Discount", kind: "credit" },
],
shippingOptions: [
{
id: "standard",
label: "Standard (5-7 days)",
selected: true,
type: "SHIPPING",
amount: { currency: "USD", value: "5.00" },
},
{
id: "express",
label: "Express (2-3 days)",
selected: false,
type: "SHIPPING",
amount: { currency: "USD", value: "12.00" },
},
],
amountBreakdown: {
itemTotal: "40.00",
shipping: "5.00",
discount: "5.00",
},
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
},
});
document
.getElementById("paypal-one-time-payment-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the nonce after the buyer approves the payment.
Handle dynamic payment updates
Update shipping and amount when the customer changes address during checkout.
Define the PayPal trigger button that the integration will use to display transaction status or messages, as shown in the one-time payment example.
Create a one-time payment session that listens for onShippingAddressChange, recalculates shipping based on the buyer's new address, calls updatePayment with the new totals, including an updated amount breakdown, and then tokenizes the approved payment once checkout is complete.
- Async/Await
async function setupDynamicPaymentUpdates() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createOneTimePaymentSession({
amount: "10.00",
currency: "USD",
intent: "capture",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
},
onShippingAddressChange: function (data) {
var newShipping =
data.shippingAddress.city === "Chicago" ? "10.00" : "5.00";
return paypal.updatePayment({
paymentId: data.orderId,
amount: (10 + parseFloat(newShipping)).toFixed(2),
currency: "USD",
});
},
});
document
.getElementById("paypal-one-time-payment-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the nonce after the buyer approves the payment.
In addition to onShippingAddressChange, you can listen for onShippingOptionsChange, which fires when the buyer selects a different shipping option instead of changing their address. The callback receives errors, orderId, and selectedShippingOption, which includes id, label, amount, type, and selected.
- onShippingOptionsChange
onShippingOptionsChange: function (data) {
// data.selectedShippingOption: { id, label, amount: { currency, value }, type, selected }
return paypal.updatePayment({
paymentId: data.orderId,
amount: recalculateTotal(data.selectedShippingOption),
currency: "USD",
});
},onShippingOptionsChange and onShippingAddressChange are each mutually exclusive with shippingCallbackUrl.
Set up Pay Later payments
Enable PayPal Pay Later financing options for eligible customers.
Define the PayPal trigger button that the integration will use to display transaction status or messages.
Initialize using a Braintree client token and create a PayPal Checkout v6 instance, then load the PayPal SDK and check eligibility using findEligibleMethods. If Pay Later is eligible for the transaction, retrieve additional configuration details using getDetails to properly configure the button component. Then use createPayLaterSession to enable Pay Later financing. Eligible customers can then pay over time with deferred payment options.
- Async/Await
async function setupPayLater() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
// Check if Pay Later is eligible for this transaction
const eligibility = await paypal.findEligibleMethods({
amount: "100.00",
currency: "USD",
countryCode: "US",
});
if (!eligibility.paylater) {
console.log("Pay Later not eligible for this transaction");
return;
}
// Get additional details for button configuration
const details = eligibility.getDetails("paylater");
const button = document.getElementById("paypal-pay-later-button");
// Configure button with details from eligibility check
button.setAttribute("countryCode", details.countryCode);
button.setAttribute("productCode", details.productCode);
const session = paypal.createPayLaterSession({
amount: "100.00",
currency: "USD",
intent: "capture",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
},
});
button.style.display = "block";
button.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs a message if Pay Later isn't eligible for the transaction, and logs the nonce after the buyer approves the payment.
Display PayPal Messages (Pay Later promotional messaging)
Display PayPal Pay Later, installment, and PayPal Credit promotional messaging near your product or checkout price using createMessages.
- HTML
<paypal-message logo-type="inline" text-color="monochrome"></paypal-message>- Async/Await
async function setupPayPalMessages() {
try {
await paypal.loadPayPalSDK();
const messagesInstance = await paypal.createMessages({
buyerCountry: "US",
currencyCode: "USD",
});
const messageEl = document.querySelector("paypal-message");
const content = await messagesInstance.fetchContent({
amount: "99.99",
onReady: function (content) {
messageEl.setContent(content);
},
});
// Update later, for example when the cart total changes:
// content.update({ amount: '149.99' });
} catch (err) {
console.error("Error loading PayPal message content:", err);
}
}The browser console logs an error if the message content fails to load.
Control PayPal button text
Control the call-to-action button text in the PayPal checkout window.
The commit parameter controls the text displayed on the call-to-action button in the PayPal member checkout window. By default, commit: true is used, which displays "Pay Now" to indicate immediate payment. When commit: false is used, the button displays "Continue" or similar language, indicating that the buyer will be returned to your site to complete additional steps before finalizing the transaction.
Use commit: false when you have a final checkout or review page that buyers need to visit after PayPal approval but before order completion.
- Async/Await
async function setupButtonText() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
// Example with commit: false for "Continue" button
const session = paypal.createOneTimePaymentSession({
amount: "10.00",
currency: "USD",
intent: "capture",
commit: false, // Shows "Continue" instead of "Pay Now"
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
// Redirect to final checkout page for review
window.location.href = "/checkout/review?nonce=" + payload.nonce;
},
});
document
.getElementById("paypal-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The buyer is redirected to the checkout review page with the nonce appended to the URL after approval.
Handle server-side shipping callbacks
Handle shipping address and option updates on your backend server.
Use the shippingCallbackUrl parameter to provide a secure HTTPS endpoint that handles shipping calculations on your server instead of client-side JavaScript callbacks. For details on how server-side shipping callbacks work, see Integrating Shipping Module.
- Async/Await
async function setupServerSideShipping() {
try {
const client = await braintree.client.create({
authorization: "CLIENT-TOKEN",
});
const paypal = await braintree.paypalCheckoutV6.create({ client: client });
await paypal.loadPayPalSDK();
const session = paypal.createOneTimePaymentSession({
amount: "10.00",
currency: "USD",
intent: "capture",
shippingCallbackUrl: "https://yourserver.com/api/paypal/shipping-callback",
onApprove: async function (data) {
const payload = await paypal.tokenizePayment({
payerID: data.payerId,
orderID: data.orderId,
});
console.log("Nonce:", payload.nonce);
},
});
document
.getElementById("paypal-button")
.addEventListener("click", function () {
session.start();
});
} catch (err) {
// Handle error
}
}The browser console logs the nonce after the buyer approves the payment.
Additional session options
The following options are available on createOneTimePaymentSession and createPayLaterSession. locale, landingPageType, enableShippingAddress, shippingAddressEditable, and riskCorrelationId are also available on createBillingAgreementSession.
| Option | Values | Description |
|---|---|---|
contactPreference | NO_CONTACT_INFO (default), RETAIN_CONTACT_INFO, UPDATE_CONTACT_INFO | Controls whether and how the buyer's contact info is shown and editable on the PayPal page. Required if using shippingAddressOverride with a different recipient. Currently available for US-based merchants only. |
locale | Locale code, such as en_US or fr_FR | Customizes the PayPal experience language and format. |
landingPageType | login, billing | Forces the PayPal landing page to the login screen or the guest checkout/billing screen. Defaults to PayPal's own selection if omitted. |
enableShippingAddress | true or false | When true, prompts the customer for a shipping address. Defaults to suppressed when omitted. |
shippingAddressEditable | true or false | Controls whether a displayed shipping address is editable. Pass false to make it read-only. Defaults to editable when omitted. |
riskCorrelationId | Risk correlation ID for advanced fraud protection. Stored and used during tokenization. | |
offerCredit | true or false | Whether to offer PayPal Credit. |
Next: Choose your integration
The rest of your configuration will be determined by how you'd like to use PayPal.
- Want a checkout from your cart or product page? Use One-Time Payments.
- Need a combination of features and Pay Later offers? Use Vaulted Payments.
- Want one-click payments for repeat customers? Have a subscription model? Use Recurring Payments.
See a detailed comparison of One-Time Payments, Vaulted Payments, and Recurring Payments.