npm.io
10.1.0 • Published 3d ago

@paypal/react-paypal-js

Licence
Apache-2.0
Version
10.1.0
Deps
3
Size
780 kB
Vulns
0
Weekly
0

react-paypal-js

React components for the PayPal JS SDK

build status npm version bundle size npm downloads apache license storybook

Are you still using the old PayPal JS SDK V5 SDK?

This documentation teaches how to use the latest PayPal JS SDK with react. For the integration using PayPal JS SDK V5 with PayPalScriptProvider, PayPalButtons, PayPalHostedFields, and BraintreePayPalButtons, see README-PAYPAL-JS-SDK-V5.md.


Why use react-paypal-js?

The Problem

Integrating PayPal into React applications requires careful handling of SDK script loading, payment session management, and UI rendering. Building a robust integration from scratch can lead to issues with timing, state management, and buyer experience.

The Solution

react-paypal-js provides a modern, hooks-based solution that abstracts away the complexities of the PayPal V6 SDK. It enforces best practices by default to ensure buyers get the best possible user experience.

Features

  • Modern Hooks API - Fine-grained control over payment sessions with usePayPalOneTimePaymentSession, useVenmoOneTimePaymentSession, and more
  • Built-in Eligibility - Automatically check which payment methods are available with useEligibleMethods()
  • Web Component Buttons - Use PayPal's optimized <paypal-button>, <venmo-button>, and <paypal-pay-later-button> web components
  • Flexible Loading - Support for string token/id, Promise-based token/id, and deferred loading patterns
  • TypeScript Support - Complete type definitions for all components and hooks
  • SSR Compatible - Built-in hydration handling for server-side rendered applications

Supported Payment Methods

  • PayPal - Standard PayPal checkout
  • Venmo - Venmo payments
  • Pay Later - PayPal's buy now, pay later option
  • PayPal Basic Card - Guest card payments without a PayPal account
  • PayPal Advanced Card - Card payments with enhanced features and customization options
  • PayPal Subscriptions - Recurring billing subscriptions
  • PayPal Save - Vault payment methods without purchase
  • PayPal Credit - PayPal Credit one-time and save payments
  • Google Pay - Native Google Pay button flow through PaymentsClient
  • Apple Pay - Native Apple Pay payments (Safari + HTTPS only)

Resources

Installation

npm install @paypal/react-paypal-js

Quick Start

import {
  PayPalProvider,
  PayPalOneTimePaymentButton,
} from "@paypal/react-paypal-js/sdk-v6";

function App() {
  return (
    <PayPalProvider
      clientId="your-client-id"
      environment="sandbox"
      components={["paypal-payments"]}
      pageType="checkout"
    >
      <CheckoutPage />
    </PayPalProvider>
  );
}

function CheckoutPage() {
  return (
    <PayPalOneTimePaymentButton
      createOrder={async () => {
        const response = await fetch("/api/create-order", {
          method: "POST",
        });
        const { orderId } = await response.json();
        return { orderId };
      }}
      onApprove={async ({ orderId }: OnApproveDataOneTimePayments) => {
        await fetch(`/api/capture-order/${orderId}`, {
          method: "POST",
        });
        console.log("Payment captured!");
      }}
    />
  );
}

PayPalProvider

The PayPalProvider component is the entry point for the V6 SDK. It handles loading the PayPal SDK, creating an instance, and running eligibility checks.

Props
Prop Type Required Description
clientToken string | Promise<string> * Client token from your server. Mutually exclusive with clientId.
clientId string | Promise<string> * Client ID from your PayPal app. Mutually exclusive with clientToken.
components Components[] No SDK components to load. Defaults to ["paypal-payments"].
pageType string No Type of page: "checkout", "product-details", "cart", "product-listing", etc.
locale string No Locale for the SDK (e.g., "en_US").
environment "sandbox" | "production" Yes Required. SDK environment. clientId does not select the environment in v6 — this prop does.
merchantId string | string[] No PayPal merchant ID(s).
clientMetadataId string No Client metadata ID for tracking.
partnerAttributionId string No Partner attribution ID (BN code).
shopperSessionId string No Shopper session ID for tracking.
testBuyerCountry string No Test buyer country code (sandbox only).
debug boolean No Enable debug mode.
dataNamespace string No Custom namespace for the SDK script data attribute.
eligibleMethodsResponse FindEligiblePaymentMethodsResponse No Server-fetched eligibility response for SDK hydration (see Server-Side Rendering).

* Either clientToken or clientId is required, but not both. They are mutually exclusive.

Available Components

The components prop accepts an array of the following values:

  • "paypal-payments" - PayPal and Pay Later buttons
  • "venmo-payments" - Venmo button
  • "paypal-guest-payments" - Guest checkout (card payments)
  • "paypal-subscriptions" - Subscription payments
  • "card-fields" - Card Fields (advanced card payment UI)
  • "googlepay-payments" - Google Pay
With Promise-based Client ID
function App() {
  // Memoize to prevent re-fetching on each render
  const clientIdPromise = useMemo(() => fetchClientId(), []);

  return (
    <PayPalProvider
      clientId={clientIdPromise}
      environment="sandbox"
      components={["paypal-payments"]}
      pageType="checkout"
    >
      <CheckoutPage />
    </PayPalProvider>
  );
}

Alternative: With Promise-based Client Token

function App() {
  // Memoize to prevent re-fetching on each render
  const tokenPromise = useMemo(() => fetchClientToken(), []);

  return (
    <PayPalProvider
      clientToken={tokenPromise}
      environment="sandbox"
      components={["paypal-payments"]}
      pageType="checkout"
    >
      <CheckoutPage />
    </PayPalProvider>
  );
}
Deferred Loading
function App() {
  const [clientId, setClientId] = useState<string>();

  useEffect(() => {
    fetchClientId().then(setClientId);
  }, []);

  return (
    <PayPalProvider
      clientId={clientId}
      environment="sandbox"
      components={["paypal-payments"]}
      pageType="checkout"
    >
      <CheckoutPage />
    </PayPalProvider>
  );
}
Tracking Loading State

Use the usePayPal hook to access the SDK loading status:

import {
  usePayPal,
  INSTANCE_LOADING_STATE,
} from "@paypal/react-paypal-js/sdk-v6";

function CheckoutPage() {
  const { loadingStatus, error } = usePayPal();

  if (loadingStatus === INSTANCE_LOADING_STATE.PENDING) {
    return <div className="spinner">Loading PayPal...</div>;
  }

  if (loadingStatus === INSTANCE_LOADING_STATE.REJECTED) {
    return (
      <div className="error">Failed to load PayPal SDK: {error?.message}</div>
    );
  }

  return <PayPalOneTimePaymentButton orderId="ORDER-123" />;
}

Button Components

PayPalOneTimePaymentButton

Renders a PayPal button for one-time payments.

import { PayPalOneTimePaymentButton } from "@paypal/react-paypal-js/sdk-v6";

<PayPalOneTimePaymentButton
  createOrder={async () => {
    const response = await fetch("/api/create-order", { method: "POST" });
    const { orderId } = await response.json();
    return { orderId };
  }}
  onApprove={async ({ orderId }: OnApproveDataOneTimePayments) => {
    await fetch(`/api/capture/${orderId}`, { method: "POST" });
    console.log("Payment approved!");
  }}
  onCancel={(data: OnCancelDataOneTimePayments) =>
    console.log("Payment cancelled")
  }
  onError={(data: OnErrorData) => console.error("Payment error:", data)}
  onComplete={(data: OnCompleteData) => console.log("Payment Flow Completed")}
/>;

Props:

Prop Type Description
orderId string Static order ID (alternative to createOrder)
createOrder () => Promise<{ orderId: string }> Async function to create an order
presentationMode "auto" | "popup" | "modal" | "redirect" Optional. How to present the payment session. Defaults to "auto".
onApprove (data) => void Called when payment is approved
onCancel () => void Called when buyer cancels
onError (error) => void Called on error
onComplete (data) => void Called when payment session completes
type "pay" | "checkout" | "buynow" | "donate" | "subscribe" Button label type
disabled boolean Disable the button
VenmoOneTimePaymentButton

Renders a Venmo button for one-time payments. Requires "venmo-payments" in the provider's components array.

import { VenmoOneTimePaymentButton } from "@paypal/react-paypal-js/sdk-v6";

<PayPalProvider
  clientId={clientId}
  environment="sandbox"
  components={["paypal-payments", "venmo-payments"]}
  pageType="checkout"
>
  <VenmoOneTimePaymentButton
    createOrder={async () => {
      const { orderId } = await createOrder();
      return { orderId };
    }}
    onApprove={(data: OnApproveDataOneTimePayments) =>
      console.log("Venmo payment approved!", data)
    }
    onCancel={(data: OnCancelDataOneTimePayments) =>
      console.log("Venmo payment cancelled", data)
    }
    onError={(data: OnErrorData) => console.error("Venmo payment error:", data)}
    onComplete={(data: OnCompleteData) =>
      console.log("Venmo payment flow completed", data)
    }
  />
</PayPalProvider>;
GooglePayOneTimePaymentButton

Renders a native Google Pay button for one-time payments. Requires "googlepay-payments" in the provider's components array.

Google Pay prerequisites:

  1. Load Google Pay JS in your app HTML shell (for example public/index.html):
<script async src="https://pay.google.com/gp/p/js/pay.js"></script>
  1. Ensure the script is available before rendering GooglePayOneTimePaymentButton, since this component depends on window.google.payments.api.PaymentsClient.
import {
  PayPalProvider,
  GooglePayOneTimePaymentButton,
  useEligibleMethods,
  INSTANCE_LOADING_STATE,
  usePayPal,
} from "@paypal/react-paypal-js/sdk-v6";

function GooglePayCheckout() {
  const { loadingStatus } = usePayPal();
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { currencyCode: "USD" },
  });

  if (loadingStatus === INSTANCE_LOADING_STATE.PENDING || isLoading) {
    return <div>Loading Google Pay...</div>;
  }

  const googlePayConfig = eligiblePaymentMethods?.isEligible("googlepay")
    ? eligiblePaymentMethods.getDetails("googlepay").config
    : null;

  if (!googlePayConfig) {
    return <div>Google Pay is not eligible for this buyer.</div>;
  }

  return (
    <GooglePayOneTimePaymentButton
      googlePayConfig={googlePayConfig}
      transactionInfo={{
        countryCode: "US",
        currencyCode: "USD",
        totalPriceStatus: "FINAL",
        totalPrice: "100.00",
      }}
      createOrder={async () => {
        const response = await fetch("/api/create-order", { method: "POST" });
        const { orderId } = await response.json();
        return { orderId };
      }}
      onApprove={(data) => console.log("Google Pay approved", data)}
      onCancel={() => console.log("Google Pay cancelled")}
      onError={(error) => console.error("Google Pay error", error)}
      buttonType="pay"
      buttonColor="default"
      buttonSizeMode="fill"
    />
  );
}

function App() {
  return (
    <PayPalProvider
      clientId="your-client-id"
      environment="sandbox"
      components={["googlepay-payments"]}
      pageType="checkout"
    >
      <GooglePayCheckout />
    </PayPalProvider>
  );
}

Props:

Prop Type Description
googlePayConfig GooglePayConfigFromFindEligibleMethods Google Pay config returned by eligiblePaymentMethods.getDetails("googlepay")
transactionInfo GooglePayTransactionInfo Google Pay transaction details (country, currency, amount, and optional display items)
createOrder () => Promise<{ orderId: string }> Async function to create an order
onApprove (data) => void | Promise<void> Called when Google Pay payment is approved
onCancel () => void Called when buyer cancels the Google Pay sheet
onError (error: Error) => void Called on setup or payment errors
environment "TEST" | "PRODUCTION" Google Pay environment (default: "TEST")
buttonType "pay" | ... Google Pay button type
buttonColor "default" | "black" | "white" Google Pay button color
buttonSizeMode "fill" | "static" Google Pay button size mode
buttonLocale string Google Pay button locale
disabled boolean Disable interaction
PayLaterOneTimePaymentButton

Renders a Pay Later button for financing options. Country code and product code are automatically populated from eligibility data, so eligibility must be fetched first — via useEligibleMethods() client-side (shown below) or the provider's eligibleMethodsResponse prop server-side.

import {
  PayLaterOneTimePaymentButton,
  useEligibleMethods,
} from "@paypal/react-paypal-js/sdk-v6";

function PayLaterCheckout() {
  // Fetch eligibility first (or hydrate server-side via eligibleMethodsResponse)
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

  if (isLoading) {
    return <Spinner />;
  }
  if (!eligiblePaymentMethods?.isEligible("paylater")) {
    return null;
  }

  return (
    <PayLaterOneTimePaymentButton
      createOrder={async () => {
        const { orderId } = await createOrder();
        return { orderId };
      }}
      onApprove={(data: OnApproveDataOneTimePayments) =>
        console.log("Pay Later approved!", data)
      }
      onCancel={(data: OnCancelDataOneTimePayments) =>
        console.log("Pay Later cancelled", data)
      }
      onError={(data: OnErrorData) => console.error("Pay Later error:", data)}
      onComplete={(data: OnCompleteData) =>
        console.log("Pay Later flow completed", data)
      }
    />
  );
}
PayPalGuestPaymentButton

Renders a guest checkout button for card payments without a PayPal account (Branded Card/Debit Card checkout). Requires "paypal-guest-payments" in the provider's components array.

import { PayPalGuestPaymentButton } from "@paypal/react-paypal-js/sdk-v6";

<PayPalProvider
  clientId={clientId}
  environment="sandbox"
  components={["paypal-payments", "paypal-guest-payments"]}
  pageType="checkout"
>
  <PayPalGuestPaymentButton
    createOrder={async () => {
      const { orderId } = await createOrder();
      return { orderId };
    }}
    onApprove={(data: OnApproveDataOneTimePayments) =>
      console.log("Guest payment approved!", data)
    }
    onCancel={(data: OnCancelDataOneTimePayments) =>
      console.log("Guest payment cancelled", data)
    }
    onError={(data: OnErrorData) => console.error("Guest payment error:", data)}
    onComplete={(data: OnCompleteData) =>
      console.log("Guest payment flow completed", data)
    }
  />
</PayPalProvider>;
PayPalSavePaymentButton

Renders a button for vaulting a payment method without making a purchase.

import { PayPalSavePaymentButton } from "@paypal/react-paypal-js/sdk-v6";

<PayPalSavePaymentButton
  createVaultToken={async () => {
    const response = await fetch("/api/create-vault-token", {
      method: "POST",
    });
    const { vaultSetupToken } = await response.json();
    return { vaultSetupToken };
  }}
  onApprove={({ vaultSetupToken }: OnApproveDataSavePayments) => {
    console.log("Payment method saved:", vaultSetupToken);
  }}
  onCancel={(data: OnCancelDataSavePayments) =>
    console.log("Save payment cancelled", data)
  }
  onError={(data: OnErrorData) => console.error("Save payment error:", data)}
  onComplete={(data: OnCompleteData) =>
    console.log("Save payment flow completed", data)
  }
/>;
PayPalSubscriptionButton

Renders a PayPal button for subscription payments. Requires "paypal-subscriptions" in the provider's components array.

import { PayPalSubscriptionButton } from "@paypal/react-paypal-js/sdk-v6";

<PayPalProvider
  clientId={clientId}
  environment="sandbox"
  components={["paypal-subscriptions"]}
  pageType="checkout"
>
  <PayPalSubscriptionButton
    createSubscription={async () => {
      const response = await fetch("/api/create-subscription", {
        method: "POST",
      });
      const { subscriptionId } = await response.json();
      return { subscriptionId };
    }}
    onApprove={(data: OnApproveDataOneTimePayments) =>
      console.log("Subscription approved:", data)
    }
    onCancel={(data: OnCancelDataOneTimePayments) =>
      console.log("Subscription cancelled", data)
    }
    onError={(data: OnErrorData) => console.error("Subscription error:", data)}
    onComplete={(data: OnCompleteData) =>
      console.log("Subscription flow completed", data)
    }
  />
</PayPalProvider>;
PayPalCreditOneTimePaymentButton

Renders a PayPal Credit button for one-time payments. The countryCode is automatically populated from eligibility data, so eligibility must be fetched first — via useEligibleMethods() client-side (shown below) or the provider's eligibleMethodsResponse prop server-side.

import {
  PayPalCreditOneTimePaymentButton,
  useEligibleMethods,
} from "@paypal/react-paypal-js/sdk-v6";

function CreditCheckout() {
  // Fetch eligibility first (or hydrate server-side via eligibleMethodsResponse)
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

  if (isLoading) {
    return <Spinner />;
  }
  if (!eligiblePaymentMethods?.isEligible("credit")) {
    return null;
  }

  return (
    <PayPalCreditOneTimePaymentButton
      createOrder={async () => {
        const response = await fetch("/api/create-order", { method: "POST" });
        const { orderId } = await response.json();
        return { orderId };
      }}
      onApprove={({ orderId }: OnApproveDataOneTimePayments) =>
        console.log("Credit payment approved:", orderId)
      }
      onCancel={(data: OnCancelDataOneTimePayments) =>
        console.log("Credit payment cancelled", data)
      }
      onError={(data: OnErrorData) =>
        console.error("Credit payment error:", data)
      }
      onComplete={(data: OnCompleteData) =>
        console.log("Credit payment flow completed", data)
      }
    />
  );
}
PayPalCreditSavePaymentButton

Renders a PayPal Credit button for saving a credit payment method (vaulting). The countryCode is automatically populated from eligibility data, so eligibility must be fetched first — via useEligibleMethods() client-side (shown below) or the provider's eligibleMethodsResponse prop server-side.

import {
  PayPalCreditSavePaymentButton,
  useEligibleMethods,
} from "@paypal/react-paypal-js/sdk-v6";

function CreditSaveCheckout() {
  // Fetch eligibility first (or hydrate server-side via eligibleMethodsResponse)
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

  if (isLoading) {
    return <Spinner />;
  }
  if (!eligiblePaymentMethods?.isEligible("credit")) {
    return null;
  }

  return (
    <PayPalCreditSavePaymentButton
      createVaultToken={async () => {
        const response = await fetch("/api/create-vault-token", {
          method: "POST",
        });
        const { vaultSetupToken } = await response.json();
        return { vaultSetupToken };
      }}
      onApprove={(data: OnApproveDataSavePayments) =>
        console.log("Credit saved:", data)
      }
      onCancel={(data: OnCancelDataSavePayments) =>
        console.log("Credit save cancelled", data)
      }
      onError={(data: OnErrorData) => console.error("Credit save error:", data)}
      onComplete={(data: OnCompleteData) =>
        console.log("Credit save flow completed", data)
      }
    />
  );
}
ApplePayOneTimePaymentButton

Renders Apple's native <apple-pay-button> web component and manages the full Apple Pay payment flow — including merchant validation, payment authorization, and order confirmation — via the PayPal SDK.

Requirements:

  • Safari browser (macOS 10.12+ / iOS 10+)
  • HTTPS connection
  • Apple Pay configured on the user's device
  • components={["applepay-payments"]} in PayPalProvider
  • Apple Pay JS SDK loaded via a script tag in your HTML:
<script
  crossorigin
  src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"
></script>

Integration steps:

  1. Check window.ApplePaySession?.canMakePayments() — only render the button if this returns true. Wrap in try-catch because it throws on non-HTTPS connections.
  2. Call useEligibleMethods() to fetch eligibility and obtain applePayConfig from getDetails("applepay").config.
  3. Pass applePayConfig explicitly to the component — it is a required prop.
import {
  PayPalProvider,
  ApplePayOneTimePaymentButton,
  useEligibleMethods,
} from "@paypal/react-paypal-js/sdk-v6";

async function createOrder() {
  const response = await fetch("/api/paypal/create-order", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      items: [{ id: "item-1", quantity: 1 }],
    }),
  });
  const data = await response.json();
  return { orderId: data.id };
}

async function onApprove(data) {
  // confirmOrder is handled internally by the hook.
  // Capture the order using the ID from the confirmation response.
  const orderId = data.approveApplePayPayment.id;
  const response = await fetch(`/api/paypal/capture/${orderId}`, {
    method: "POST",
  });
  const result = await response.json();
  console.log("Apple Pay payment captured:", result);
}

function ApplePayCheckout() {
  // Step 1: Check if Apple Pay is supported by the browser/device.
  // canMakePayments() throws on non-HTTPS, so wrap in try-catch.
  let canUseApplePay = false;
  try {
    canUseApplePay =
      typeof window !== "undefined" &&
      !!window.ApplePaySession?.canMakePayments();
  } catch {
    // Not available (e.g., non-HTTPS environment)
  }

  // Step 2: Fetch eligibility.
  // Note: hooks must be called unconditionally (React rules of hooks).
  // To avoid the eligibility API call on unsupported browsers, split the
  // check and the button into separate components in your app.
  const { eligiblePaymentMethods, isLoading, error } = useEligibleMethods({
    payload: { currencyCode: "USD" },
  });

  if (!canUseApplePay) {
    return <div>Apple Pay is not available in this browser.</div>;
  }
  if (isLoading) {
    return <div>Loading...</div>;
  }
  if (error) {
    return <div>Error: {error.message}</div>;
  }

  // Step 3: Check merchant eligibility and get config.
  const isEligible = eligiblePaymentMethods?.isEligible("applepay");
  if (!isEligible) {
    return <div>Apple Pay is not eligible.</div>;
  }

  const applePayConfig = eligiblePaymentMethods?.getDetails("applepay")?.config;
  if (!applePayConfig) {
    return null;
  }

  return (
    <ApplePayOneTimePaymentButton
      applePayConfig={applePayConfig}
      paymentRequest={{
        countryCode: "US",
        currencyCode: "USD",
        requiredBillingContactFields: [
          "name",
          "phone",
          "email",
          "postalAddress",
        ],
        requiredShippingContactFields: [],
        total: {
          label: "Demo (Card is not charged)",
          amount: "20.00",
          type: "final",
        },
      }}
      createOrder={createOrder}
      onApprove={onApprove}
      onCancel={() => console.log("Apple Pay cancelled")}
      onError={(error) => console.error("Apple Pay error:", error)}
      applePaySessionVersion={4}
      buttonstyle="black"
      type="buy"
    />
  );
}

export default function App() {
  return (
    <PayPalProvider
      clientId="YOUR_CLIENT_ID"
      environment="sandbox"
      components={["applepay-payments"]}
      pageType="checkout"
    >
      <ApplePayCheckout />
    </PayPalProvider>
  );
}

Props:

Prop Type Required Description
applePayConfig ApplePayConfig Yes Config object from useEligibleMethods().getDetails("applepay").config
paymentRequest ApplePayPaymentRequest Yes Apple Pay payment request (countryCode, currencyCode, total, etc.)
createOrder () => Promise<{ orderId: string }> Yes Called during authorization to create the PayPal order
onApprove (data: ConfirmOrderResponse) => void Yes Called after payment confirmation; use data.approveApplePayPayment.id to capture
onCancel () => void No Called when the buyer dismisses the payment sheet
onError (error: Error) => void No Called on errors (merchant validation failure, network error, etc.)
applePaySessionVersion number No Apple Pay JS API version passed to ApplePaySession (minimum: 4)
buttonstyle "black" | "white" | "white-outline" No Visual style of the Apple Pay button
type "pay" | "buy" | "set-up" | "donate" | "check-out" | "book" | "subscribe" No Label displayed on the button
locale string No Locale for the button label (e.g., "en", "fr", "ja")
disabled boolean No Disables the button

Key differences from other PayPal buttons:

  • No presentationMode — Apple controls the native payment sheet UI
  • No eager order creation (orderId prop) — orders are always created lazily during payment authorization
  • applePayConfig is required and must be obtained from useEligibleMethods()
  • onApprove receives ConfirmOrderResponse — capture the order using data.approveApplePayPayment.id

Braintree PayPal Integration

Braintree merchants use BraintreePayPalProvider instead of PayPalProvider to integrate PayPal via Braintree's paypalCheckoutV6 module. This provider initializes the Braintree client, creates a PayPal Checkout V6 instance, and loads the PayPal SDK — then exposes the instance to child components and hooks via React context.

Resources:

Prerequisites
<script src="https://js.braintreegateway.com/web/3.142.0/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.142.0/js/paypal-checkout-v6.min.js"></script>
BraintreePayPalProvider

Wraps child components with Braintree context. On mount it validates the namespace, creates a Braintree client instance, creates a paypalCheckoutV6 instance, and calls loadPayPalSDK(). On unmount it calls teardown() to release resources.

import { useState, useEffect } from "react";
import { BraintreePayPalProvider } from "@paypal/react-paypal-js/sdk-v6";

declare global {
  interface Window {
    braintree: BraintreeV6Namespace;
  }
}

function App() {
  const [clientToken, setClientToken] = useState<string | undefined>(undefined);

  useEffect(() => {
    fetch("/auth/browser-safe-client-token")
      .then((res) => res.json())
      .then(({ clientToken }) => setClientToken(clientToken));
  }, []);

  if (!clientToken) {
    return <div>Loading...</div>;
  }

  return (
    <BraintreePayPalProvider
      namespace={window.braintree}
      braintreeClientToken={clientToken}
    >
      <CheckoutPage />
    </BraintreePayPalProvider>
  );
}

Props:

Prop Type Required Description
namespace BraintreeV6Namespace Yes The braintree global namespace — must expose client.create and paypalCheckoutV6.create functions
braintreeClientToken string | undefined Yes Client token from your server (generated via the Braintree SDK)
children ReactNode Yes Child components

Note: The namespace prop must have referential stability across renders. An unstable reference (e.g., creating the object inline) will cause re-initialization on every render. Use a module-level constant, useRef, or useMemo.

BraintreePayPalOneTimePaymentButton

Renders a <paypal-button> web component for one-time Braintree PayPal payments. Internally uses useBraintreePayPalOneTimePaymentSession to create and start payment sessions.

import {
  BraintreePayPalProvider,
  BraintreePayPalOneTimePaymentButton,
  useBraintreePayPal,
} from "@paypal/react-paypal-js/sdk-v6";
import type { BraintreeApprovalData } from "@paypal/react-paypal-js/sdk-v6";

function CheckoutPage() {
  const { braintreePayPalCheckoutInstance } = useBraintreePayPal();

  return (
    <BraintreePayPalOneTimePaymentButton
      amount="100.00"
      currency="USD"
      intent="capture"
      type="pay"
      onApprove={async (data: BraintreeApprovalData) => {
        const { nonce } =
          await braintreePayPalCheckoutInstance!.tokenizePayment(data);
        // Send nonce to your server
        await fetch("/api/braintree/checkout", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ nonce }),
        });
      }}
      onCancel={(data) => console.log("Cancelled", data)}
      onError={(err) => console.error("Error", err)}
    />
  );
}

function App() {
  const [clientToken, setClientToken] = useState<string | undefined>(undefined);

  useEffect(() => {
    fetch("/auth/browser-safe-client-token")
      .then((res) => res.json())
      .then(({ clientToken }) => setClientToken(clientToken));
  }, []);

  if (!clientToken) {
    return <div>Loading...</div>;
  }

  return (
    <BraintreePayPalProvider
      namespace={window.braintree}
      braintreeClientToken={clientToken}
    >
      <CheckoutPage />
    </BraintreePayPalProvider>
  );
}

Props:

Prop Type Required Description
amount string Yes Payment amount (e.g., "100.00")
currency string Yes ISO 4217 currency code (e.g., "USD")
onApprove (data: BraintreeApprovalData) => Promise<void> Yes Called when buyer approves — tokenize the payment here
intent "authorize" | "capture" | "order" No Payment intent (default: "capture")
commit boolean No true for "Pay Now", false for "Continue"
offerCredit boolean No Offer PayPal Credit as default funding
onCancel (data: BraintreeOnCancelData) => void No Called when buyer cancels
onError (err: Error) => void No Called on errors
onShippingAddressChange (data: BraintreeShippingAddressChangeData) => Promise<void> No Called when buyer changes shipping address
onShippingOptionsChange (data: BraintreeShippingOptionsChangeData) => Promise<void> No Called when buyer selects a shipping option
lineItems BraintreeLineItem[] No Line items for the transaction
shippingOptions BraintreeShippingOption[] No Available shipping options
amountBreakdown BraintreeAmountBreakdown No Breakdown of the total amount (item total, shipping, tax, etc.)
userAuthenticationEmail string No Pre-fill the PayPal login email
displayName string No Merchant name displayed in the PayPal lightbox
presentationMode BraintreePresentationMode No UI mode: "auto", "popup", "modal", "redirect", etc.
returnUrl string No Return URL (required for "direct-app-switch" presentation mode)
cancelUrl string No Cancel URL (required for "direct-app-switch" presentation mode)
type "pay" | "checkout" | "buynow" | "donate" | "subscribe" No Button label type (default: "pay")
disabled boolean No Disable the button
BraintreePayPalBillingAgreementButton

Renders a <paypal-button> for vault-only flows — saving a buyer's PayPal account as a payment method without an immediate charge. Supports subscription plans via planType and planMetadata.

import {
  BraintreePayPalBillingAgreementButton,
  useBraintreePayPal,
} from "@paypal/react-paypal-js/sdk-v6";
import type { BraintreeApprovalData } from "@paypal/react-paypal-js/sdk-v6";

function BillingAgreementButton() {
  const { braintreePayPalCheckoutInstance } = useBraintreePayPal();

  return (
    <BraintreePayPalBillingAgreementButton
      type="subscribe"
      billingAgreementDescription="Monthly subscription to Premium"
      planType="SUBSCRIPTION"
      planMetadata={{
        currencyIsoCode: "USD",
        name: "Premium Plan",
        billingCycles: [
          {
            billingFrequency: 1,
            billingFrequencyUnit: "MONTH",
            numberOfExecutions: 0,
            sequence: 1,
            startDate: "2026-07-01T00:00:00Z",
            trial: false,
            pricingScheme: { pricingModel: "FIXED", price: "9.99" },
          },
        ],
      }}
      onApprove={async (data: BraintreeApprovalData) => {
        const { nonce } =
          await braintreePayPalCheckoutInstance!.tokenizePayment(data);
        await fetch("/api/braintree/vault", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ nonce }),
        });
      }}
      onCancel={(data) => console.log("Cancelled", data)}
      onError={(err) => console.error("Error", err)}
    />
  );
}

Props:

Prop Type Required Description
onApprove (data: BraintreeApprovalData) => Promise<void> Yes Called when buyer approves — tokenize with data.billingToken
billingAgreementDescription string No Description shown to the buyer (e.g., "Monthly subscription")
planType "RECURRING" | "SUBSCRIPTION" | "UNSCHEDULED" | "INSTALLMENTS" No Type of billing plan
planMetadata BraintreePlanMetadata No Subscription plan details including billing cycles
amount string No Amount for vault-with-purchase flows
currency string No Currency for vault-with-purchase flows
offerCredit boolean No Offer PayPal Credit
userAction "CONTINUE" | "COMMIT" | "SETUP_NOW" No Button action label
displayName string No Merchant name in the PayPal lightbox
shippingAddressOverride Record<string, unknown> No Pre-collected shipping address
onCancel (data: BraintreeOnCancelData) => void No Called when buyer cancels
onError (err: Error) => void No Called on errors
presentationMode BraintreePresentationMode No UI mode: "auto", "popup", "modal", "redirect", etc.
returnUrl string No Return URL (required for app-switch modes)
cancelUrl string No Cancel URL (required for app-switch modes)
type "pay" | "checkout" | "buynow" | "donate" | "subscribe" No Button label type (default: "pay")
disabled boolean No Disable the button
BraintreePayPalCheckoutWithVaultButton

Renders a <paypal-button> for a combined flow — charging the buyer and saving their payment method in a single transaction (one-time payment + billing agreement consent).

import {
  BraintreePayPalCheckoutWithVaultButton,
  useBraintreePayPal,
} from "@paypal/react-paypal-js/sdk-v6";
import type { BraintreeApprovalData } from "@paypal/react-paypal-js/sdk-v6";

function CheckoutWithVaultButton() {
  const { braintreePayPalCheckoutInstance } = useBraintreePayPal();

  return (
    <BraintreePayPalCheckoutWithVaultButton
      amount="49.99"
      currency="USD"
      intent="capture"
      type="pay"
      billingAgreementDetails={{
        description: "Monthly subscription to Products!",
      }}
      onApprove={async (data: BraintreeApprovalData) => {
        const { nonce } =
          await braintreePayPalCheckoutInstance!.tokenizePayment(data);
        await fetch("/api/braintree/checkout-and-vault", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ nonce }),
        });
      }}
      onCancel={() => console.log("Cancelled")}
      onError={(err) => console.error("Error", err)}
    />
  );
}

Props:

Prop Type Required Description
amount string Yes Payment amount (e.g., "49.99")
currency string Yes ISO 4217 currency code
onApprove (data: BraintreeApprovalData) => Promise<void> Yes Called when buyer approves — tokenize the payment here
intent "authorize" | "capture" | "order" No Payment intent (default: "capture")
commit boolean No true for "Pay Now", false for "Continue"
billingAgreementDetails { description?: string } No Billing agreement details shown to the buyer
onCancel () => void No Called when buyer cancels
onError (err: Error) => void No Called on errors
onShippingAddressChange (data: BraintreeShippingAddressChangeData) => Promise<void> No Called when buyer changes shipping address
onShippingOptionsChange (data: BraintreeShippingOptionsChangeData) => Promise<void> No Called when buyer selects a shipping option
lineItems BraintreeLineItem[] No Line items for the transaction
shippingOptions BraintreeShippingOption[] No Available shipping options
amountBreakdown BraintreeAmountBreakdown No Breakdown of the total amount
userAuthenticationEmail string No Pre-fill the PayPal login email
displayName string No Merchant name in the PayPal lightbox
presentationMode BraintreePresentationMode No UI mode: "auto", "popup", "modal", "redirect", etc.
returnUrl string No Return URL (required for "direct-app-switch" presentation mode)
cancelUrl string No Cancel URL (required for "direct-app-switch" presentation mode)
type "pay" | "checkout" | "buynow" | "donate" | "subscribe" No Button label type (default: "pay")
disabled boolean No Disable the button
BraintreePayPalPayLaterButton

Renders a <paypal-pay-later-button> web component for Braintree PayPal Pay Later (Buy Now, Pay Later) financing. Internally uses useBraintreePayPalPayLaterSession to create and start Pay Later sessions.

Unlike the other Braintree buttons, this button requires eligibility data. It reads countryCode and productCode from useBraintreePayPal().eligiblePaymentMethods, which is populated by useBraintreeEligibleMethods. Fetch eligibility first — without it the button renders hidden (display: none).

import {
  BraintreePayPalPayLaterButton,
  useBraintreePayPal,
  useBraintreeEligibleMethods,
  INSTANCE_LOADING_STATE,
} from "@paypal/react-paypal-js/sdk-v6";
import type { BraintreeApprovalData } from "@paypal/react-paypal-js/sdk-v6";

function PayLaterCheckout() {
  const { loadingStatus, braintreePayPalCheckoutInstance } =
    useBraintreePayPal();

  // Fetch eligibility before rendering the button
  const { eligiblePaymentMethods, isLoading } = useBraintreeEligibleMethods({
    amount: "100.00",
    currency: "USD", // required
    countryCode: "US",
    paymentFlow: "ONE_TIME_PAYMENT",
  });

  if (isLoading || loadingStatus === INSTANCE_LOADING_STATE.PENDING) {
    return <Spinner />;
  }
  if (!eligiblePaymentMethods?.paylater) {
    return null;
  }

  return (
    <BraintreePayPalPayLaterButton
      amount="100.00"
      currency="USD"
      onApprove={async (data: BraintreeApprovalData) => {
        const { nonce } =
          await braintreePayPalCheckoutInstance!.tokenizePayment(data);
        // Send nonce to your server
        await fetch("/api/braintree/checkout", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ nonce }),
        });
      }}
      onCancel={() => console.log("Cancelled")}
      onError={(err) => console.error("Error", err)}
    />
  );
}

Render PayLaterCheckout inside a BraintreePayPalProvider (see BraintreePayPalProvider).

Props:

Prop Type Required Description
amount string Yes Payment amount (e.g., "100.00")
currency string Yes ISO 4217 currency code (e.g., "USD")
onApprove (data: BraintreeApprovalData) => Promise<void> Yes Called when buyer approves — tokenize the payment here
intent "authorize" | "capture" | "order" No Payment intent (default: "capture")
onCancel () => void No Called when buyer cancels
onComplete () => void No Called when the payment flow completes
onError (err: Error) => void No Called on errors
onShippingAddressChange (data: BraintreeShippingAddressChangeData) => Promise<void> No Called when buyer changes shipping address
onShippingOptionsChange (data: BraintreeShippingOptionsChangeData) => Promise<void> No Called when buyer selects a shipping option
lineItems BraintreeLineItem[] No Line items for the transaction
shippingOptions BraintreeShippingOption[] No Available shipping options
shippingCallbackUrl string No URL for server-side shipping callbacks
shippingAddressOverride BraintreeShippingAddressOverride No Pre-collected shipping address
amountBreakdown BraintreeAmountBreakdown No Breakdown of the total amount (item total, shipping, tax, etc.)
contactPreference "NO_CONTACT_INFO" | "RETAIN_CONTACT_INFO" | "UPDATE_CONTACT_INFO" No How buyer contact information is handled
userAuthenticationEmail string No Pre-fill the PayPal login email
returnUrl string No Return URL (required for "direct-app-switch" presentation mode)
cancelUrl string No Cancel URL (required for "direct-app-switch" presentation mode)
displayName string No Merchant name displayed in the PayPal lightbox
presentationMode BraintreePresentationMode No UI mode: "auto", "popup", "modal", "redirect", etc.
disabled boolean No Disable the button
Braintree Hooks
useBraintreePayPal()

Accesses the Braintree PayPal context. Returns the checkout instance, loading status, and error state. Must be used within a BraintreePayPalProvider.

import {
  useBraintreePayPal,
  INSTANCE_LOADING_STATE,
} from "@paypal/react-paypal-js/sdk-v6";

function CustomCheckout() {
  const { braintreePayPalCheckoutInstance, loadingStatus, error, isHydrated } =
    useBraintreePayPal();

  if (loadingStatus === INSTANCE_LOADING_STATE.PENDING) {
    return <div>Initializing Braintree...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  // Use braintreePayPalCheckoutInstance directly for custom flows
}

Returns:

Property Type Description
braintreePayPalCheckoutInstance BraintreePayPalCheckoutInstance | null The checkout instance (null while loading)
eligiblePaymentMethods BraintreeEligibilityResult | null Eligibility cached by useBraintreeEligibleMethods (null until fetched)
loadingStatus INSTANCE_LOADING_STATE "pending", "resolved", or "rejected"
error Error | null Initialization error, if any
isHydrated boolean true after client-side hydration
useBraintreeEligibleMethods(props)

Fetches Braintree PayPal eligibility for the given checkout options by calling findEligibleMethods() on the shared checkout instance, and caches the result in BraintreePayPalProvider context (where buttons read it via useBraintreePayPal().eligiblePaymentMethods). Fetches are deduplicated by (instance, options) and re-run when the options change. Required before rendering eligibility-gated buttons such as BraintreePayPalPayLaterButton.

import {
  useBraintreePayPal,
  useBraintreeEligibleMethods,
  BraintreePayPalOneTimePaymentButton,
  BraintreePayPalPayLaterButton,
  INSTANCE_LOADING_STATE,
} from "@paypal/react-paypal-js/sdk-v6";

function CheckoutButtons() {
  const { loadingStatus, braintreePayPalCheckoutInstance } =
    useBraintreePayPal();
  const { eligiblePaymentMethods, isLoading, error } =
    useBraintreeEligibleMethods({
      amount: "100.00",
      currency: "USD",
      countryCode: "US",
      paymentFlow: "ONE_TIME_PAYMENT",
    });

  if (isLoading || loadingStatus === INSTANCE_LOADING_STATE.PENDING) {
    return <Spinner />;
  }
  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <>
      <BraintreePayPalOneTimePaymentButton
        amount="100.00"
        currency="USD"
        // onApprove not shown in this example
        // see other examples for usage of tokenizePayment in onApprove
        onApprove={onApprove}
      />
      {eligiblePaymentMethods?.paylater && (
        <BraintreePayPalPayLaterButton
          amount="100.00"
          currency="USD"
          onApprove={onApprove}
        />
      )}
    </>
  );
}

Props (BraintreeFindEligibleMethodsOptions):

Prop Type Required Description
currency string Yes ISO 4217 currency code (e.g., "USD")
amount string No Checkout amount used for eligibility checks
countryCode string No Buyer country code (e.g., "US")
paymentFlow "ONE_TIME_PAYMENT" | "VAULT_WITH_PAYMENT" | "VAULT_WITHOUT_PAYMENT" | "RECURRING_PAYMENT" No The flow eligibility is being checked for

Returns:

Property Type Description
eligiblePaymentMethods BraintreeEligibilityResult | null Eligibility result, or null until fetched
isLoading boolean true while the instance is initializing or eligibility is being fetched
error Error | null Fetch- or provider-level error, if any

BraintreeEligibilityResult exposes the booleans paypal, paylater, and credit, plus getDetails(method) which returns the countryCode and productCode the buttons consume.

useBraintreePayPalOneTimePaymentSession(props)

Creates a one-time payment session. Returns a handleClick function to start the PayPal flow. Accepts the same props as BraintreePayPalOneTimePaymentButton (minus type and disabled).

import { useBraintreePayPalOneTimePaymentSession } from "@paypal/react-paypal-js/sdk-v6";

function CustomPayButton() {
  const { handleClick, isPending, error } =
    useBraintreePayPalOneTimePaymentSession({
      amount: "50.00",
      currency: "USD",
      onApprove: async (data) => {
        // tokenize and process
      },
    });

  return (
    <button onClick={handleClick} disabled={isPending}>
      Pay with PayPal
    </button>
  );
}

Returns: { handleClick: () => void, isPending: boolean, error: Error | null }

useBraintreePayPalBillingAgreementSession(props)

Creates a billing agreement session for vault flows. Returns a handleClick function to

Keywords