UNPKG

@paypal/react-paypal-js

Version:
1,218 lines (982 loc) 44.5 kB
# react-paypal-js > React components for the [PayPal JS SDK](https://docs.paypal.ai/developer/how-to/sdk/js/v6/configuration) <div class="badges"> <a href="https://github.com/paypal/paypal-js/actions/workflows/main.yml"><img src="https://img.shields.io/github/actions/workflow/status/paypal/paypal-js/main.yml?branch=main&logo=github&style=flat-square" alt="build status"></a> <a href="https://www.npmjs.com/package/@paypal/react-paypal-js"><img src="https://img.shields.io/npm/v/@paypal/react-paypal-js.svg?style=flat-square" alt="npm version"></a> <a href="https://bundlephobia.com/result?p=@paypal/react-paypal-js"><img src="https://img.shields.io/bundlephobia/minzip/@paypal/react-paypal-js.svg?style=flat-square" alt="bundle size"></a> <a href="https://www.npmtrends.com/@paypal/react-paypal-js"><img src="https://img.shields.io/npm/dm/@paypal/react-paypal-js.svg?style=flat-square" alt="npm downloads"></a> <a href="https://github.com/paypal/react-paypal-js/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@paypal/react-paypal-js.svg?style=flat-square" alt="apache license"></a> <a href="https://paypal.github.io/paypal-js/web-sdk-v6-react-storybook/"><img src="https://raw.githubusercontent.com/storybooks/brand/master/badge/badge-storybook.svg" alt="storybook"></a> </div> --- > **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](./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 ## Resources - [PayPal V6 SDK Documentation](https://docs.paypal.ai/payments/methods/paypal/sdk/js/v6/paypal-checkout) - [React Sample Integration](https://github.com/paypal-examples/v6-web-sdk-sample-integration/tree/main/client/prebuiltPages/react) - Full working example with Node.js backend - [Live Demo](https://v6-web-sdk-sample-integration-server.fly.dev/client/prebuiltPages/react/dist/) - Try the sample integration in sandbox mode - [PayPal Server SDK](https://www.npmjs.com/package/@paypal/paypal-server-sdk) - For backend integration - [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/) - [PayPal Sandbox Test Accounts](https://developer.paypal.com/dashboard/accounts) - [PayPal Sandbox Card Testing](https://developer.paypal.com/tools/sandbox/card-testing/) ## Installation ```sh npm install @paypal/react-paypal-js ``` ## Quick Start ```tsx import { PayPalProvider, PayPalOneTimePaymentButton, } from "@paypal/react-paypal-js/sdk-v6"; function App() { return ( <PayPalProvider clientId="your-client-id" 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"` | No | SDK environment. | | `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](#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) ### With Promise-based Client ID ```tsx function App() { // Memoize to prevent re-fetching on each render const clientIdPromise = useMemo(() => fetchClientId(), []); return ( <PayPalProvider clientId={clientIdPromise} components={["paypal-payments"]} pageType="checkout" > <CheckoutPage /> </PayPalProvider> ); } ``` Alternative: With Promise-based Client Token ```tsx function App() { // Memoize to prevent re-fetching on each render const tokenPromise = useMemo(() => fetchClientToken(), []); return ( <PayPalProvider clientToken={tokenPromise} components={["paypal-payments"]} pageType="checkout" > <CheckoutPage /> </PayPalProvider> ); } ``` ### Deferred Loading ```tsx function App() { const [clientId, setClientId] = useState<string>(); useEffect(() => { fetchClientId().then(setClientId); }, []); return ( <PayPalProvider clientId={clientId} components={["paypal-payments"]} pageType="checkout" > <CheckoutPage /> </PayPalProvider> ); } ``` ### Tracking Loading State Use the `usePayPal` hook to access the SDK loading status: ```tsx 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. ```tsx 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"` | How to present the payment session (default: `"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. ```tsx import { VenmoOneTimePaymentButton } from "@paypal/react-paypal-js/sdk-v6"; <PayPalProvider clientId={clientId} 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>; ``` ### PayLaterOneTimePaymentButton Renders a Pay Later button for financing options. Country code and product code are automatically populated from eligibility data. ```tsx import { PayLaterOneTimePaymentButton } from "@paypal/react-paypal-js/sdk-v6"; <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. ```tsx import { PayPalGuestPaymentButton } from "@paypal/react-paypal-js/sdk-v6"; <PayPalProvider clientId={clientId} 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. ```tsx 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. ```tsx import { PayPalSubscriptionButton } from "@paypal/react-paypal-js/sdk-v6"; <PayPalProvider clientId={clientId} 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. ```tsx import { PayPalCreditOneTimePaymentButton } from "@paypal/react-paypal-js/sdk-v6"; <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). ```tsx import { PayPalCreditSavePaymentButton } from "@paypal/react-paypal-js/sdk-v6"; <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) } />; ``` ## 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. ```tsx import { PayPalProvider, PayPalCardFieldsProvider, } from "@paypal/react-paypal-js/sdk-v6"; function App() { return ( <PayPalProvider clientToken="your-client-token" 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](#paypalcardfieldsprovider) component. ```tsx 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](#paypalcardfieldsprovider) component. ```tsx 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](#paypalcardfieldsprovider) component. ```tsx import { PayPalCardCvvField } from "@paypal/react-paypal-js/sdk-v6"; <PayPalCardCvvField placeholder="CVV" containerStyles={{ height: "3rem", marginBottom: "1rem" }} />; ``` ### Field Component Props All field components ([`PayPalCardNumberField`](#paypalcardnumberfield), [`PayPalCardExpiryField`](#paypalcardexpiryfield), [`PayPalCardCvvField`](#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. ```tsx 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. ```tsx 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: ```tsx 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). ```tsx 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](#paypalcardfieldsprovider) component. ```tsx 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) | #### usePayPalOneTimePaymentSession ```tsx import { usePayPalOneTimePaymentSession } from "@paypal/react-paypal-js/sdk-v6"; function CustomPayPalButton() { const { isPending, error, handleClick } = usePayPalOneTimePaymentSession({ createOrder: async () => { const { orderId } = await createOrder(); return { orderId }; }, presentationMode: "auto", 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 ```tsx 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 ```tsx import { usePayLaterOneTimePaymentSession } 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), }); return <paypal-pay-later-button onClick={() => handleClick()} />; } ``` #### usePayPalGuestPaymentSession ```tsx 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 ```tsx import { usePayPalSavePaymentSession } from "@paypal/react-paypal-js/sdk-v6"; function CustomPayPalSaveButton() { const { handleClick } = usePayPalSavePaymentSession({ createVaultToken: async () => { const { vaultSetupToken } = await createVaultToken(); return { vaultSetupToken }; }, presentationMode: "popup", 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 ```tsx 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. ```tsx import { usePayPalCreditOneTimePaymentSession } 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), }); return <paypal-credit-button onClick={() => handleClick()} />; } ``` #### usePayPalCreditSavePaymentSession For saving PayPal Credit as a payment method. ```tsx import { usePayPalCreditSavePaymentSession } 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), }); return <paypal-credit-button onClick={() => handleClick()} />; } ``` ### Card Fields Session Hooks Use these hooks to handle the Card Fields payment flows. The hooks must be used within a [`PayPalCardFieldsProvider`](#paypalcardfieldsprovider) component. | Hook | Payment Type | | ------------------------------------------ | ------------ | | `usePayPalCardFieldsOneTimePaymentSession` | One-time | | `usePayPalCardFieldsSavePaymentSession` | Save/Vault | #### usePayPalCardFieldsOneTimePaymentSession Hook for managing one-time payment Card Fields sessions. ```tsx 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. ```tsx 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> ); } ``` ## Web Components The V6 SDK uses web components for rendering buttons. These are automatically typed when you import from `@paypal/react-paypal-js/sdk-v6`. ### Available Web Components | Component | Description | | ------------------------------- | -------------------------- | | `<paypal-button>` | PayPal payment button | | `<venmo-button>` | Venmo payment button | | `<paypal-pay-later-button>` | Pay Later button | | `<paypal-basic-card-container>` | Guest checkout container | | `<paypal-basic-card-button>` | Guest checkout button | | `<paypal-credit-button>` | PayPal Credit button | | `<paypal-message>` | PayPal messaging component | ### Button Types The `type` prop controls the button label: - `"pay"` - "Pay with PayPal" (default) - `"checkout"` - "Checkout with PayPal" - `"buynow"` - "Buy Now" - `"donate"` - "Donate" - `"subscribe"` - "Subscribe" ```tsx <paypal-button type="checkout" onClick={handleClick} /> ``` ## Server-Side Rendering The `useFetchEligibleMethods` function is available from the server export path for pre-fetching eligibility data on the server. Pass the response to `PayPalProvider` via the `eligibleMethodsResponse` prop to avoid a client-side eligibility fetch. ```tsx // app/checkout/page.tsx (Next.js server component) import { useFetchEligibleMethods } from "@paypal/react-paypal-js/sdk-v6/server"; import { PayPalProvider } from "@paypal/react-paypal-js/sdk-v6"; export default async function CheckoutPage() { const eligibleMethodsResponse = await useFetchEligibleMethods({ environment: "sandbox", headers: { Authorization: `Bearer ${clientToken}`, "Content-Type": "application/json", }, payload: { purchase_units: [{ amount: { currency_code: "USD", value: "100.00" } }], }, }); return ( <PayPalProvider clientId={clientId} pageType="checkout" eligibleMethodsResponse={eligibleMethodsResponse} > <CheckoutForm /> </PayPalProvider> ); } ``` ## Migration from v8.x (Legacy SDK) The v9.0.0 release introduces the V6 SDK with a new API. Here are the key differences: | v8.x (Legacy) | v9.0.0 (V6 SDK) | | ------------------------------- | ---------------------------------------------------- | | `PayPalScriptProvider` | `PayPalProvider` | | `PayPalButtons` | `PayPalOneTimePaymentButton` or hooks | | `options={{ clientId }}` | `clientId={clientId}` or `clientToken={clientToken}` | | `createOrder` returns `orderId` | `createOrder` returns `{ orderId }` | | `@paypal/react-paypal-js` | `@paypal/react-paypal-js/sdk-v6` | ### Before (v8.x) ```tsx import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js"; <PayPalScriptProvider options={{ clientId: "test" }}> <PayPalButtons createOrder={() => { return fetch("/api/orders", { method: "POST" }) .then((res) => res.json()) .then((order) => order.id); }} onApprove={(data) => { return fetch(`/api/orders/${data.orderID}/capture`, { method: "POST", }); }} /> </PayPalScriptProvider>; ``` ### After (v9.0.0) ```tsx import { PayPalProvider, PayPalOneTimePaymentButton, } from "@paypal/react-paypal-js/sdk-v6"; <PayPalProvider clientId={clientId} pageType="checkout"> <PayPalOneTimePaymentButton createOrder={async () => { const res = await fetch("/api/orders", { method: "POST" }); const order = await res.json(); return { orderId: order.id }; }} onApprove={async ({ orderId }) => { await fetch(`/api/orders/${orderId}/capture`, { method: "POST" }); }} /> </PayPalProvider>; ``` For the legacy API documentation, see [README-v8.md](./README-v8.md). ## TypeScript This package includes full TypeScript definitions. Import types from the same path: ```tsx import type { // Web component props ButtonProps, PayLaterButtonProps, PayPalBasicCardButtonProps, PayPalCreditButtonProps, // Session hook props UsePayPalOneTimePaymentSessionProps, UseVenmoOneTimePaymentSessionProps, UsePayLaterOneTimePaymentSessionProps, UsePayPalGuestPaymentSessionProps, UsePayPalSubscriptionPaymentSessionProps, UsePayPalSavePaymentSessionProps, UsePayPalCreditOneTimePaymentSessionProps, UsePayPalCreditSavePaymentSessionProps, // Button component props PayPalSubscriptionButtonProps, PayPalCreditOneTimePaymentButtonProps, PayPalCreditSavePaymentButtonProps, // Enums INSTANCE_LOADING_STATE, } from "@paypal/react-paypal-js/sdk-v6"; ``` ### Web Component Types The package automatically extends JSX types to include PayPal web components. No additional configuration is needed for React 17, 18, or 19. ## Browser Support This library supports all modern browsers. See the [PayPal browser support documentation](https://developer.paypal.com/docs/business/checkout/reference/browser-support/) for the full list.