Skip to main content
This guide demonstrates how to integrate one-time payments using the JavaScript SDK v6 in your web application, including PayPal, Pay Later, and PayPal Credit options.

Prerequisites

Set up your PayPal account:
  • Create a PayPal developer, personal, or business account
  • Visit the PayPal Developer Dashboard
  • Create a sandbox application to obtain your Client ID and Secret
In your project root folder, create a .env file with your PayPal credentials:
PAYPAL_SANDBOX_CLIENT_ID=your_client_id
PAYPAL_SANDBOX_CLIENT_SECRET=your_client_secret

Integration overview

The PayPal one-time payments integration supports multiple payment methods:
  1. PayPal - Standard PayPal payments
  2. Pay Later - Buy now, pay later financing options
  3. PayPal Credit - Credit-based payment option
The integration consists of four main steps:
  1. Initialize the SDK with payment components
  2. Check eligibility for each payment method
  3. Create payment sessions with callback handlers
  4. Start payment flow with presentation mode configuration

Set up your front end

This is the recommended approach for most implementations. It includes all payment methods with eligibility logic and automatic fallback handling.

Key components

The following are key components of the integration.

PayPal SDK Instance

  • Purpose: Main entry point for PayPal functionality
  • Components: Includes paypal-payments component
  • Authentication: Requires client token from server

Eligibility Check

  • Purpose: Determines available payment methods
  • Factors: User location, currency, account status, device type
  • Implementation: Always check before showing payment buttons

Payment Sessions

  • PayPal: Standard PayPal payments
  • Pay Later: Financing options with specific product codes
  • PayPal Credit: Credit-based payments with country-specific configuration

Web Components

  • <paypal-button>: Standard PayPal payment button
  • <paypal-pay-later-button>: Pay Later financing button
  • <paypal-credit-button>: PayPal Credit button

Build an HTML page

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>One-Time Payment - Recommended Integration - PayPal JavaScript SDK</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      .buttons-container {
        display: flex;
        flex-direction: column;
        gap: 12px;
      }
    </style>
  </head>
  <body>
    <h1>One-Time Payment Recommended Integration</h1>

    <div class="buttons-container">
      <!-- All buttons are hidden until eligibility is confirmed -->
      <paypal-button id="paypal-button" type="pay" hidden></paypal-button>
      <paypal-pay-later-button id="paylater-button" hidden></paypal-pay-later-button>
      <paypal-credit-button id="paypal-credit-button" hidden></paypal-credit-button>
    </div>
    
    <script src="app.js"></script>

    <!-- Load PayPal SDK -->
    <script
      async
      src="https://www.sandbox.paypal.com/web-sdk/v6/core"
      onload="onPayPalWebSdkLoaded()"
    ></script>
  </body>
</html>

Build the JavaScript

async function onPayPalWebSdkLoaded() {
  try {
    // Get client token for authentication
    const clientToken = await getBrowserSafeClientToken();
    
    // Create PayPal SDK instance
    const sdkInstance = await window.paypal.createInstance({
      clientToken,
      components: ["paypal-payments"],
      pageType: "checkout",
    });

    // Check eligibility for all payment methods
    const paymentMethods = await sdkInstance.findEligibleMethods({
      currencyCode: "USD",
    });

    // Setup PayPal button if eligible
    if (paymentMethods.isEligible("paypal")) {
      setupPayPalButton(sdkInstance);
    }

    // Setup Pay Later button if eligible
    if (paymentMethods.isEligible("paylater")) {
      const paylaterPaymentMethodDetails = paymentMethods.getDetails("paylater");
      setupPayLaterButton(sdkInstance, paylaterPaymentMethodDetails);
    }

    // Setup PayPal Credit button if eligible
    if (paymentMethods.isEligible("credit")) {
      const paypalCreditPaymentMethodDetails = paymentMethods.getDetails("credit");
      setupPayPalCreditButton(sdkInstance, paypalCreditPaymentMethodDetails);
    }
  } catch (error) {
    console.error("SDK initialization error:", error);
  }
}

// Shared payment session options for all payment methods
const paymentSessionOptions = {
  // Called when user approves a payment 
  async onApprove(data) {
    console.log("Payment approved:", data);
    try {
      const orderData = await captureOrder({
        orderId: data.orderId,
      });
      console.log("Payment captured successfully:", orderData);
      handlePaymentSuccess(orderData);
    } catch (error) {
      console.error("Payment capture failed:", error);
      handlePaymentError(error);
    }
  },
  
  // Called when user cancels a payment
  onCancel(data) {
    console.log("Payment cancelled:", data);
    handlePaymentCancellation();
  },
  
  // Called when an error occurs during payment
  onError(error) {
    console.error("Payment error:", error);
    handlePaymentError(error);
  },
};

// Setup standard PayPal button
async function setupPayPalButton(sdkInstance) {
  const paypalPaymentSession = sdkInstance.createPayPalOneTimePaymentSession(
    paymentSessionOptions,
  );

  const paypalButton = document.querySelector("#paypal-button");
  paypalButton.removeAttribute("hidden");

  paypalButton.addEventListener("click", async () => {
    try {
      await paypalPaymentSession.start(
        { presentationMode: "auto" }, // Auto-detects best presentation mode
        createOrder(),
      );
    } catch (error) {
      console.error("PayPal payment start error:", error);
      handlePaymentError(error);
    }
  });
}

// Set up Pay Later button
async function setupPayLaterButton(sdkInstance, paylaterPaymentMethodDetails) {
  const paylaterPaymentSession = sdkInstance.createPayLaterOneTimePaymentSession(
    paymentSessionOptions
  );

  const { productCode, countryCode } = paylaterPaymentMethodDetails;
  const paylaterButton = document.querySelector("#paylater-button");

  // Configure button with Pay Later specific details
  paylaterButton.productCode = productCode;
  paylaterButton.countryCode = countryCode;
  paylaterButton.removeAttribute("hidden");

  paylaterButton.addEventListener("click", async () => {
    try {
      await paylaterPaymentSession.start(
        { presentationMode: "auto" },
        createOrder(),
      );
    } catch (error) {
      console.error("Pay Later payment start error:", error);
      handlePaymentError(error);
    }
  });
}

// Setup PayPal Credit button
async function setupPayPalCreditButton(sdkInstance, paypalCreditPaymentMethodDetails) {
  const paypalCreditPaymentSession = sdkInstance.createPayPalCreditOneTimePaymentSession(
    paymentSessionOptions
  );

  const { countryCode } = paypalCreditPaymentMethodDetails;
  const paypalCreditButton = document.querySelector("#paypal-credit-button");

  // Configure button with PayPal Credit specific details
  paypalCreditButton.countryCode = countryCode;
  paypalCreditButton.removeAttribute("hidden");

  paypalCreditButton.addEventListener("click", async () => {
    try {
      await paypalCreditPaymentSession.start(
        { presentationMode: "auto" },
        createOrder(),
      );
    } catch (error) {
      console.error("PayPal Credit payment start error:", error);
      handlePaymentError(error);
    }
  });
}

Key difference between v6 and previous versions

For more control over order details:

// In v6, this must return an object with a signature of { orderId: "YOUR_ORDER_ID" }
  return fetch("/paypal-api/checkout/orders/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(orderPayload),
  })
    .then(response => response.json())
    .then(data => ({ orderId: data.id })); /// <-- Important! 
}

Choose a presentation mode

The JavaScript SDK v6 supports multiple presentation modes:
{ presentationMode: "auto" }
  • Automatically selects the best presentation mode
  • Tries popup first, falls back to modal if popups are blocked
  • Best for most use cases
{ presentationMode: "popup" }
  • Opens PayPal in a popup window
  • Provides seamless user experience
  • May be blocked by popup blockers
{ presentationMode: "modal" }
  • Opens PayPal in an overlay modal
  • This is only recommended for Webview scenarios
  • Do not use in desktop web scenarios as this integration has limitations on cookies which can affect user authentication

Redirect mode

{ presentationMode: "redirect" }
  • Full-page redirect to PayPal
  • Best for mobile devices
  • Requires return/cancel URL configuration

Payment handler mode (experimental)

{ presentationMode: "payment-handler" }
  • Uses the browser’s Payment Handler API
  • Modern browsers only
  • Provides native payment experience

Set up your backend

Set up your backend to call the following endpoints.

1. Client Token endpoint

Get a client token to call the Orders API for checkout.
// GET /paypal-api/auth/browser-safe-client-token
async function getBrowserSafeClientToken() {
  const response = await fetch("/paypal-api/auth/browser-safe-client-token", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const { accessToken } = await response.json();
  return accessToken;
}

2. Create Order endpoint

Creating an order initializes a payment request and reserves the funds, but doesn’t transfer money. After the buyer authorizes an order, you capture the funds.
// POST /paypal-api/checkout/orders/create-with-sample-data (for quick testing)
async function createOrder() {
  const response = await fetch(
    "/paypal-api/checkout/orders/create-with-sample-data",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    },
  );
  const { id } = await response.json();
  return { orderId: id };
}

// POST /paypal-api/checkout/orders/create (for custom orders)
async function createCustomOrder(orderPayload) {
  const response = await fetch("/paypal-api/checkout/orders/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(orderPayload),
  });
  const { id } = await response.json();
  return { orderId: id };
}

3. Capture Order endpoint

After a buyer authorizes payment, capture an order to transfer funds from the buyer to the merchant account.
// POST /paypal-api/checkout/orders/{orderId}/capture
async function captureOrder({ orderId }) {
  const response = await fetch(
    `/paypal-api/checkout/orders/${orderId}/capture`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    },
  );
  const data = await response.json();
  return data;
}

Handle errors

function handlePaymentError(error) {
  console.error("Payment error details:", error);
  
  // Show user-friendly error messages based on error type
  const errorMessage = getErrorMessage(error);
  showErrorToUser(errorMessage);
  
  // Optionally provide retry mechanism
  showRetryOption();
}

function getErrorMessage(error) {
  switch (error.code) {
    case 'PAYMENT_CANCELLED':
      return 'Payment was cancelled. Please try again.';
    case 'ERR_DEV_UNABLE_TO_OPEN_POPUP':
      return 'Popup blocked. Please allow popups and try again.';
    case 'ERR_FLOW_PAYMENT_HANDLER_BROWSER_INCOMPATIBLE':
      return 'Payment method not supported in this browser. Trying alternative method.';
    case 'NETWORK_ERROR':
      return 'Network error occurred. Please check your connection and try again.';
    default:
      return 'An unexpected error occurred. Please try again.';
  }
}

function handlePaymentSuccess(orderData) {
  console.log("Payment successful:", orderData);
  
  // Show success message
  showSuccessMessage('Payment completed successfully!');
  
  // Redirect to success page or update UI
  redirectToSuccessPage(orderData.id);
  
  // Track conversion for analytics
  trackPaymentSuccess(orderData);
}

function handlePaymentCancellation() {
  console.log("Payment cancelled by user");
  
  // Show cancellation message
  showInfoMessage('Payment was cancelled. Your order is still in your cart.');
  
  // Return user to checkout or cart
  returnToCheckout();
}

Best practices

Keep sensitive operations server-side, validate all payment data, and provide clear feedback to users throughout the payment flow.

Security

  • Obtain client tokens from your secure server
  • Never expose PayPal client secrets in frontend code
  • All payment processing happens through PayPal’s secure servers
  • Never pass up item total from browser - this can be manipulated
  • Validate order details on your server before capture

User experience

  • Always check eligibility before showing payment buttons
  • Provide clear loading states during payment processing
  • Handle popup blockers gracefully with { presentationMode:auto }
  • Show appropriate error messages for different failure scenarios

Performance

  • Initialize the SDK early but avoid blocking page load
  • Cache client tokens appropriately
  • Use presentation mode fallback strategies

Testing

  • Test across different browsers and devices
  • Verify popup blocker handling
  • Test all callback scenarios (approve, cancel, error)
  • Validate eligibility logic works correctly

Production checklist

  • Replace sandbox URLs with production URLs
  • Update environment configuration for production
  • Test eligibility on production environment
  • Implement comprehensive error handling
  • Add analytics and conversion tracking
  • Test across different devices and browsers
  • Implement proper loading and success states
  • Set up monitoring and alerting
  • Verify webhook endpoints are configured
  • Test order capture and fulfillment process

Support

For additional support and questions:
I