npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paypal/react-paypal-js

v10.3.0

Published

React components for the PayPal JS SDK

Downloads

1,674,584

Readme

react-paypal-js

React components for the PayPal JS SDK


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.


Table of Contents

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)
  • Braintree PayPal - PayPal checkout for Braintree merchants via the paypalCheckoutV6 module

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

All button components share a common set of props for order creation, payment callbacks, and presentation, documented once in Common Button Props below. Each button section then documents only the props unique to that button.

Common Button Props

These props are accepted by the standard PayPal button components. Buttons with different flows (vault, subscription, Google Pay, Apple Pay) override some of these — see the individual sections.

| 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 |

Vault and subscription buttons replace order creation: PayPalSavePaymentButton and PayPalCreditSavePaymentButton use vaultSetupToken/createVaultToken instead of orderId/createOrder, and PayPalSubscriptionButton uses subscriptionId/createSubscription.

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: Accepts the full set of Common Button Props (orderId/createOrder, presentationMode, onApprove, onCancel, onError, onComplete, type, disabled). No additional props.

VenmoOneTimePaymentButton

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

Props: Accepts all Common Button Props. No additional props.

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: Accepts the order-creation and callback props from Common Button Props (createOrder, onApprove, onCancel, onError, disabled), plus the following Google Pay–specific 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) | | 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 |

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.

Props: Accepts all Common Button Props. countryCode and productCode are populated automatically from eligibility data (no props to pass).

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.

Props: Accepts the order-creation and callback props from Common Button Props (orderId/createOrder, presentationMode, onApprove, onCancel, onError, onComplete, disabled). This button has no type prop.

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.

Props: Accepts the callback and presentation props from Common Button Props (presentationMode, onApprove, onCancel, onError, onComplete, type, disabled), but replaces order creation with:

| Prop | Type | Required | Description | | ------------------ | -------------------------------------------- | -------- | ------------------------------------------------------------ | | createVaultToken | () => Promise<{ vaultSetupToken: string }> | Yes* | Async function that returns a vault setup token | | vaultSetupToken | string | Yes* | Static vault setup token (alternative to createVaultToken) |

* Provide exactly one of createVaultToken or vaultSetupToken. onApprove receives OnApproveDataSavePayments (with vaultSetupToken).

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.

Props: Accepts the callback and presentation props from Common Button Props (presentationMode, onApprove, onCancel, onError, onComplete, type, disabled; type defaults to "subscribe"), but replaces order creation with:

| Prop | Type | Required | Description | | -------------------- | ------------------------------------------- | -------- | ------------------------------------------------------------ | | createSubscription | () => Promise<{ subscriptionId: string }> | Yes* | Async function that returns a subscription ID | | subscriptionId | string | Yes* | Static subscription ID (alternative to createSubscription) |

* Provide exactly one of createSubscription or subscriptionId.

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.

Props: Accepts all Common Button Props. countryCode is populated automatically from eligibility data (no prop to pass).

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.

Props: Accepts the callback and presentation props from Common Button Props (presentationMode, onApprove, onCancel, onError, onComplete, disabled), but replaces order creation with createVaultToken/vaultSetupToken (same as PayPalSavePaymentButton). countryCode is populated automatically from eligibility data.

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: Apple Pay is a specialized native button whose callbacks differ from the Common Button Props (notably, onApprove receives a ConfirmOrderResponse). Its full prop set is listed below.

| 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

Payment Flow

  1. User clicks a payment button
  2. handleClick() starts the payment session
  3. createOrder callback creates an order via your backend API
  4. PayPal opens the checkout experience (popup/modal/redirect)
  5. On approval, onApprove callback captures the order via the backend
  6. Success/error handling displays the result to the user

Card Fields Components

Card Fields components provide customizable card input fields for collecting payment details directly on your page.

Requires "card-fields" in the provider's components array.

PayPalCardFieldsProvider

Wraps card field components and manages the Card Fields session.

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

function App() {
  return (
    <PayPalProvider
      clientToken="your-client-token"
      environment="sandbox"
      components={["card-fields"]}
      pageType="checkout"
    >
      <CheckoutForm />
    </PayPalProvider>
  );
}

function CheckoutForm() {
  return (
    <PayPalCardFieldsProvider>
      <CardPaymentForm />
    </PayPalCardFieldsProvider>
  );
}

Props:

| Prop | Type | Required | Description | | --------------------- | ----------------- | -------- | --------------------------------------------------------------------------------- | | amount | OrderAmount | No | Amount for the card transaction (e.g., { value: "10.00", currencyCode: "USD" }) | | isCobrandedEligible | boolean | No | Enable co-branded card eligibility | | blur | (event) => void | No | Callback when a field loses focus | | change | (event) => void | No | Callback when field value changes | | focus | (event) => void | No | Callback when field receives focus | | empty | (event) => void | No | Callback when field becomes empty | | notempty | (event) => void | No | Callback when field becomes non-empty | | validitychange | (event) => void | No | Callback when field validity changes | | cardtypechange | (event) => void | No | Callback when detected card type changes | | inputsubmit | (event) => void | No | Callback when submit key is pressed in field |

PayPalCardNumberField

Renders a card number input field. Must be used within a PayPalCardFieldsProvider component.

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

<PayPalCardNumberField
  placeholder="Card number"
  containerStyles={{ height: "3rem", marginBottom: "1rem" }}
/>;

PayPalCardExpiryField

Renders a card expiry input field. Must be used within a PayPalCardFieldsProvider component.

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

<PayPalCardExpiryField
  placeholder="MM/YY"
  containerStyles={{ height: "3rem", marginBottom: "1rem" }}
/>;

PayPalCardCvvField

Renders a CVV input field. Must be used within a PayPalCardFieldsProvider component.

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

<PayPalCardCvvField
  placeholder="CVV"
  containerStyles={{ height: "3rem", marginBottom: "1rem" }}
/>;

Field Component Props

All field components (PayPalCardNumberField, PayPalCardExpiryField, PayPalCardCvvField) accept the same set of props. They combine container styling properties with CardField-specific configuration options.

| Prop | Type | Required | Description | | ------------------------- | --------------------- | -------- | ---------------------------------------------- | | placeholder | string | No | Placeholder text for the field | | label | string | No | Label text for the field | | style | MerchantStyleObject | No | Style object for the field | | ariaLabel | string | No | ARIA label for accessibility | | ariaDescription | string | No | ARIA description for accessibility | | ariaInvalidErrorMessage | string | No | ARIA error message when field is invalid | | containerStyles | React.CSSProperties | No | CSS styles for the field container wrapper | | containerClassName | string | No | CSS class name for the field container wrapper |

Payment Flow: Card Fields

  1. User enters card number, expiry, and CVV in the card fields
  2. User clicks your submit button
  3. createOrder creates an order via your backend API
  4. submit(orderId) processes the card payment with the order ID
  5. submitResponse object gets updated with the payment result
  6. Handle submit response based on payment result

Hooks API

usePayPal

Returns the PayPal context including the SDK instance and loading status.

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

function MyComponent() {
  const {
    sdkInstance, // The PayPal SDK instance
    eligiblePaymentMethods, // Eligible payment methods
    loadingStatus, // PENDING | RESOLVED | REJECTED
    error, // Any initialization error
    isHydrated, // SSR hydration status
  } = usePayPal();

  const isPending = loadingStatus === INSTANCE_LOADING_STATE.PENDING;
  const isReady = loadingStatus === INSTANCE_LOADING_STATE.RESOLVED;

  // ...
}

useEligibleMethods

Returns eligible payment methods and loading state. Use this to conditionally render payment buttons based on eligibility. This hook also updates the PayPalProvider reducer with Eligibility Output from the SDK, enabling built-in eligibility features in the UI Button components.

View the Find Eligible Methods API reference for the underlying REST endpoint details.

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

function PaymentOptions() {
  const { eligiblePaymentMethods, isLoading, error } = useEligibleMethods();

  if (isLoading) {
    return <div>Checking eligibility...</div>;
  }

  const isPayPalEligible = eligiblePaymentMethods?.isEligible("paypal");
  const isVenmoEligible = eligiblePaymentMethods?.isEligible("venmo");
  const isPayLaterEligible = eligiblePaymentMethods?.isEligible("paylater");

  return (
    <div>
      {isPayPalEligible && <PayPalOneTimePaymentButton {...props} />}
      {isVenmoEligible && <VenmoOneTimePaymentButton {...props} />}
      {isPayLaterEligible && <PayLaterOneTimePaymentButton {...props} />}
    </div>
  );
}

Stale Eligibility Data Prevention

When navigating between different payment flows (e.g., from a save payment page with paymentFlow: "VAULT_WITHOUT_PAYMENT" to a checkout page with paymentFlow: "ONE_TIME_PAYMENT"), isLoading will return true while the new eligibility data is being fetched. This prevents stale buttons from flashing before the updated eligibility response arrives.

If your app uses a single PayPalProvider across multiple routes with different paymentFlow values, always check isLoading before rendering eligibility-dependent buttons:

const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
  payload: { currencyCode: "USD", paymentFlow: "ONE_TIME_PAYMENT" },
});

// Guard eligibility-dependent buttons with isLoading to avoid rendering
// buttons based on stale data from a previous payment flow
const isPayLaterEligible =
  !isLoading && eligiblePaymentMethods?.isEligible("paylater");

usePayPalMessages

Hook for integrating PayPal messaging (Pay Later promotions).

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

function PayLaterMessage() {
  const { error, isReady, handleFetchContent, handleCreateLearnMore } =
    usePayPalMessages({
      buyerCountry: "US",
      currencyCode: "USD",
    });

  // Use to display financing messages
}

usePayPalCardFields

Returns the Card Fields instance initialization errors. Must be used within a PayPalCardFieldsProvider component.

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

function CardFields() {
  const { error } = usePayPalCardFields();

  useEffect(() => {
    if (error) {
      // Handle error logic
      console.error("Error initializing PayPal Card Fields: ", error);
    }
  }, [error]);

  return <CardPaymentForm />;
}

Payment Session Hooks

For advanced use cases where you need full control over the payment flow, use the session hooks directly with web components.

Note: One-time payment session hooks (e.g., usePayPalOneTimePaymentSession) accept either a static orderId or a createOrder callback — they are mutually exclusive. Use orderId when you've already created the order, or createOrder to defer order creation until the buyer clicks. The same pattern applies to save payment hooks with vaultSetupToken vs createVaultToken.

| Hook | Payment Type | | -------------------------------------- | ------------------- | | usePayPalOneTimePaymentSession | PayPal | | useVenmoOneTimePaymentSession | Venmo | | usePayLaterOneTimePaymentSession | Pay Later | | usePayPalGuestPaymentSession | Basic Card | | usePayPalSubscriptionPaymentSession | Subscriptions | | usePayPalSavePaymentSession | Save Payment Method | | usePayPalCreditOneTimePaymentSession | Credit (One-time) | | usePayPalCreditSavePaymentSession | Credit (Save) | | useGooglePayOneTimePaymentSession | Google Pay | | useApplePayOneTimePaymentSession | Apple Pay |

usePayPalOneTimePaymentSession

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

function CustomPayPalButton() {
  const { isPending, error, handleClick } = usePayPalOneTimePaymentSession({
    createOrder: async () => {
      const { orderId } = await createOrder();
      return { orderId };
    },
    onApprove: (data: OnApproveDataOneTimePayments) =>
      console.log("Approved:", data),
    onCancel: (data: OnCancelDataOneTimePayments) => console.log("Cancelled"),
    onError: (data: OnErrorData) => console.error(data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  return (
    <paypal-button
      onClick={() => handleClick()}
      type="pay"
      disabled={isPending || error !== null}
    />
  );
}

useVenmoOneTimePaymentSession

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

function CustomVenmoButton() {
  const { handleClick } = useVenmoOneTimePaymentSession({
    createOrder: async () => {
      const { orderId } = await createOrder();
      return { orderId };
    },
    onApprove: (data: OnApproveDataOneTimePayments) =>
      console.log("Approved:", data),
    onCancel: (data: OnCancelDataOneTimePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  return <venmo-button onClick={() => handleClick()} />;
}

usePayLaterOneTimePaymentSession

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

function CustomPayLaterButton() {
  const { handleClick } = usePayLaterOneTimePaymentSession({
    createOrder: async () => {
      const { orderId } = await createOrder();
      return { orderId };
    },
    onApprove: (data: OnApproveDataOneTimePayments) =>
      console.log("Approved:", data),
    onCancel: (data: OnCancelDataOneTimePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  // Fetch eligibility to obtain the countryCode/productCode the button needs
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

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

  const payLaterDetails = eligiblePaymentMethods.getDetails("paylater");

  return (
    <paypal-pay-later-button
      onClick={() => handleClick()}
      countryCode={payLaterDetails?.countryCode}
      productCode={payLaterDetails?.productCode}
    />
  );
}

usePayPalGuestPaymentSession

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

function CustomPayPalGuestButton() {
  const { handleClick, buttonRef } = usePayPalGuestPaymentSession({
    createOrder: async () => {
      const { orderId } = await createOrder();
      return { orderId };
    },
    onApprove: (data: OnApproveDataOneTimePayments) =>
      console.log("Approved:", data),
    onCancel: (data: OnCancelDataOneTimePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  return (
    <paypal-basic-card-container>
      <paypal-basic-card-button ref={buttonRef} onClick={() => handleClick()} />
    </paypal-basic-card-container>
  );
}

usePayPalSavePaymentSession

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

function CustomPayPalSaveButton() {
  const { handleClick } = usePayPalSavePaymentSession({
    createVaultToken: async () => {
      const { vaultSetupToken } = await createVaultToken();
      return { vaultSetupToken };
    },
    onApprove: (data: OnApproveDataSavePayments) => console.log("Saved:", data),
    onCancel: (data: OnCancelDataSavePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  return <paypal-button onClick={() => handleClick()} type="pay" />;
}

usePayPalSubscriptionPaymentSession

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

function CustomPayPalSubscriptionButton() {
  const { handleClick } = usePayPalSubscriptionPaymentSession({
    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("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  return <paypal-button onClick={() => handleClick()} type="subscribe" />;
}

usePayPalCreditOneTimePaymentSession

For PayPal Credit one-time payments.

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

function CustomPayPalCreditButton() {
  const { handleClick } = usePayPalCreditOneTimePaymentSession({
    createOrder: async () => {
      const { orderId } = await createOrder();
      return { orderId };
    },
    onApprove: (data: OnApproveDataOneTimePayments) =>
      console.log("Credit approved:", data),
    onCancel: (data: OnCancelDataOneTimePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  // Fetch eligibility to obtain the countryCode the button needs
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

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

  const creditDetails = eligiblePaymentMethods.getDetails("credit");

  return (
    <paypal-credit-button
      onClick={() => handleClick()}
      countryCode={creditDetails?.countryCode}
    />
  );
}

usePayPalCreditSavePaymentSession

For saving PayPal Credit as a payment method.

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

function CustomPayPalCreditSaveButton() {
  const { handleClick } = usePayPalCreditSavePaymentSession({
    createVaultToken: async () => {
      const { vaultSetupToken } = await createVaultSetupToken();
      return { vaultSetupToken };
    },
    onApprove: (data: OnApproveDataSavePayments) =>
      console.log("Credit approved:", data),
    onCancel: (data: OnCancelDataSavePayments) =>
      console.log("Cancelled", data),
    onError: (data: OnErrorData) => console.error("Error:", data),
    onComplete: (data: OnCompleteData) =>
      console.log("Payment session complete", data),
  });

  // Fetch eligibility to obtain the countryCode the button needs
  const { eligiblePaymentMethods, isLoading } = useEligibleMethods({
    payload: { purchase_units: [{ amount: { currency_code: "USD" } }] },
  });

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

  const creditDetails = eligiblePaymentMethods.getDetails("credit");

  return (
    <paypal-credit-button
      onClick={() => handleClick()}
      countryCode={creditDetails?.countryCode}
    />
  );
}

useApplePayOneTimePaymentSession

For advanced use cases where you need full control over the Apple Pay flow. Most integrations should use ApplePayOneTimePaymentButton instead.

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

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

  const applePayConfig =
    eligiblePaymentMethods?.getDetails("applepay")?.config ?? null;

  const { isPending, error, handleClick } = useApplePayOneTimePaymentSession({
    applePayConfig,
    paymentRequest: {
      countryCode: "US",
      currencyCode: "USD",
      total: { label: "My Store", amount: "100.00", type: "final" },
    },
    applePaySessionVersion: 4,
    createOrder: async () => {
      const response = await fetch("/api/paypal/create-order", {
        method: "POST",
      });
      const data = await response.json();
      return { orderId: data.id };
    },
    onApprove: async (data) => {
      const orderId = data.approveApplePayPayment.id;
      await fetch(`/api/paypal/capture/${orderId}`, { method: "POST" });
    },
    onError: (err) => console.error("Apple Pay error:", err),
  });

  if (isLoading || !applePayConfig) {
    return null;
  }
  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <apple-pay-button
      buttonstyle="black"
      type="buy"
      locale="en"
      onClick={() => handleClick()}
      disabled={isPending}
    />
  );
}

Card Fields Session Hooks

Use these hooks to handle the Card Fields payment flows. The hooks must be used within a PayPalCardFieldsProvider component.

| Hook | Payment Type | | ------------------------------------------ | ------------ | | usePayPalCardFieldsOneTimePaymentSession | One-time | | usePayPalCardFieldsSavePaymentSession | Save/Vault |

usePayPalCardFieldsOneTimePaymentSession

Hook for managing one-time payment Card Fields sessions.

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

function CardPaymentForm() {
  const { submit, submitResponse, error } =
    usePayPalCardFieldsOneTimePaymentSession();

  useEffect(() => {
    if (error) {
      // Handle submit error logic
      console.error("Error submitting PayPal Card Fields payment", error);
    }
  }, [error]);

  useEffect(() => {
    if (!submitResponse) {
      return;
    }

    const { orderId, message } = submitResponse.data;

    switch (submitResponse.state) {
      case "succeeded":
        // Handle submit success logic
        console.log(`One time payment succeeded: orderId: ${orderId}`);
        break;
      case "failed":
        // Handle submit failed response logic
        console.error(
          `One time payment failed: orderId: ${orderId}, message: ${message}`,
        );
        break;
    }
  }, [submitResponse]);

  const handleSubmit = async () => {
    const orderId = await createOrder();
    await submit(orderId);
  };

  return (
    <div>
      <PayPalCardNumberField />
      <PayPalCardExpiryField />
      <PayPalCardCvvField />
      <button onClick={handleSubmit}>Pay</button>
    </div>
  );
}

usePayPalCardFieldsSavePaymentSession

Hook for managing save payment Card Fields sessions.

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

function CardPaymentForm() {
  const { submit, submitResponse, error } =
    usePayPalCardFieldsSavePaymentSession();

  useEffect(() => {
    if (error) {
      // Handle submit error logic
      console.error(
        "Error submitting PayPal Card Fields payment method",
        error,
      );
    }
  }, [error]);

  useEffect(() => {
    if (!submitResponse) {
      return;
    }

    const { vaultSetupToken, message } = submitResponse.data;

    switch (submitResponse.state) {
      case "succeeded":
        // Handle submit success logic
        console.log(
          `Save payment method succeeded: vaultSetupToken: ${vaultSetupToken}`,
        );
        break;
      case "failed":
        // Handle submit failed response logic
        console.error(
          `Save payment method failed: vaultSetupToken: ${vaultSetupToken}, message: ${message}`,
        );
        break;
    }
  }, [submitResponse]);

  const handleSubmit = async () => {
    const { vaultSetupToken } = await createCardVaultToken();
    await submit(vaultSetupToken);
  };

  return (
    <div>
      <PayPalCardNumberField />
      <PayPalCardExpiryField />
      <PayPalCardCvvField />
      <button onClick={handleSubmit}>Save Payment Method</button>
    </div>
  );
}

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/c