Skip to content

PayPal

Accept PayPal, Venmo, and Pay Later payments with automatic popup handling and one-click vaulting for returning customers.

Overview

PayPal is automatically included in the drop-in when enabled in your session. The drop-in handles all PayPal setup, button rendering, popup windows, and payment processing automatically.

Key benefits

  • PayPal appears automatically in the drop-in when it's enabled in your session configuration.
  • The drop-in handles all PayPal setup, so you don't need PayPal-specific code.
  • PayPal uses the same onSuccess and onError callbacks as other payment methods for a unified integration.
  • Customers can pay using PayPal, Venmo, or Pay Later financing options.
  • Returning customers can use one-click payments when their PayPal account is saved.
  • Standard PayPal branding is included automatically, ensuring a familiar checkout experience.

How it works

When a customer selects PayPal:

  1. The customer clicks "PayPal" in the accordion. The PayPal popup opens.
  2. The customer logs in to their PayPal account.
  3. The customer approves the payment in the PayPal interface.
  4. The popup closes automatically.
  5. The payment is processed through Unity.
  6. Your onSuccess callback fires.

Configuration

Configure PayPal-specific settings for the PayPal button, checkout flow, and shipping integration.

Configuration properties

The following properties are available for PayPal configuration:

Property Description
fundingSources
PaypalFundingSources[]
Funding sources to display ('paypal', 'paylater', 'venmo'). Falls back to session.allowedFundingTypes.wallets.paypal.allowedFundingOptions if not specified
enableOneClickPayment
boolean
Enable one-click payment for returning PayPal users (requires vaulting)
locale
string
Locale for PayPal button (e.g., 'en_US', 'fr_FR', 'de_DE'). Format is language_COUNTRY
payeeEmailAddress
string
Payee email address for the transaction
paymentDescription
string
Description of the payment displayed to the buyer
buyerCountry
string
Buyer's country code (e.g., 'US', 'GB', 'FR')
consentComponent
boolean
Show/hide consent checkbox for saving payment method. Defaults to true
shippingPreference
ShippingPreference
Shipping preference: 'GetFromFile', 'NoShipping', or 'SetProvidedAddress'
shippingOption
string
Selected shipping option ID
useBuiltInChangePreferredPaymentMethod
boolean
Use built-in change preferred payment method functionality
onShippingAddressChange
(data, actions) => void
Called when shipping address changes. Use actions.reject() to reject invalid addresses and update pricing dynamically
onShippingOptionsChange
(data, actions) => void
Called when shipping option changes. Use actions.reject() to reject specific shipping options

Settings used from global configuration

PayPal inherits the following settings from methodConfig.global:

Property Description
onGetConsent
(paymentMethod) => boolean
Controls whether to show consent checkbox for PayPal (used for vaulting)
onCancel
(paymentMethod, data) => void
Called when user cancels PayPal payment (closes popup)

Complete example

This example shows a full PayPal configuration with global callbacks, shipping integration, and all PayPal-specific options:

methodConfig: {
  // Global callbacks that apply to PayPal
  global: {
    // Control consent checkbox for PayPal
    onGetConsent: (paymentMethod: PaymentMethod) => {
      // Show consent checkbox for PayPal to save payment method
      if (paymentMethod === PaymentMethod.Paypal) {
        return true;
      }
      return false;
    },
    
    // Handle PayPal cancellation
    onCancel: (paymentMethod: PaymentMethod, data: any) => {
      if (paymentMethod === PaymentMethod.Paypal) {
        console.log('PayPal payment cancelled', data);
        
        // Track analytics
        trackEvent('paypal_cancelled', {
          timestamp: Date.now()
        });
        
        // Update UI
        showNotification('PayPal payment was cancelled. Please try again.');
      }
    }
  },
  
  paypal: {
    // Funding sources to display
    fundingSources: ['paypal', 'paylater', 'venmo'],
    
    // Enable one-click payment for returning users
    enableOneClickPayment: true,
    
    // Locale for PayPal button and checkout experience
    locale: 'en_US',
    
    // Payee email address
    payeeEmailAddress: 'merchant@example.com',
    
    // Payment description shown to buyer
    paymentDescription: 'Purchase from Demo Store',
    
    // Buyer's country code
    buyerCountry: 'US',
    
    // Show/hide consent checkbox for saving the payment method
    consentComponent: true,
    
    // Shipping preference
    shippingPreference: 'GetFromFile', // or 'NoShipping' or 'SetProvidedAddress'
    
    // Use built-in change preferred payment method
    useBuiltInChangePreferredPaymentMethod: false,
    
    // Callback when shipping address changes
    onShippingAddressChange: (data, actions) => {
      console.log('New shipping address:', data.shippingAddress);
      
      // Validate address - reject addresses outside US
      if (data.shippingAddress.countryCode !== 'US') {
        return actions.reject(data.errors.COUNTRY_ERROR);
      }
    },
    
    // Callback when shipping option changes
    onShippingOptionsChange: (data, actions) => {
      console.log('Shipping option selected:', data.selectedShippingOption);
      
      // Optionally reject certain shipping options
      if (data.selectedShippingOption.id === '2') {
        return actions.reject(data.errors.METHOD_UNAVAILABLE);
      }
    }
  }
}

PayPal requirements

PayPal requires the following to function correctly:

  • Browser compatibility: PayPal works on all modern browsers (Chrome, Firefox, Safari, Edge).
  • Customer setup: The customer must have a PayPal account (created during checkout if needed).
  • HTTPS: Your website must be served over HTTPS.
  • Unity Portal configuration: PayPal must be enabled and configured in the Unity Portal.
  • Entry type: PayPal only supports entryType: "Ecom".

One-click payments require the buyer to have previously authorised your merchant account. Authorisations must be captured within 29 days for PayPal (7 days for Venmo).

Implementation

PayPal works through the standard implementation, with no PayPal-specific code needed:

import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';

// Get session from backend (with PayPal enabled)
const response = await fetch('/api/create-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});

const result = await response.json();

if (!result.success || !result.data) {
  console.error('Failed to create session:', result.error);
  throw new Error('Session creation failed');
}

const sessionData = result.data;

// Initialise Drop-in
const checkoutDropIn = CheckoutDropIn.initialize({
  environment: 'test',
  session: sessionData, // Must include paypal in allowedFundingTypes
  ownerId: 'MERCHANT-1',
  ownerType: 'MerchantGroup',
  transactionData: {
    currency: 'USD',
    amount: 99.99,
    entryType: 'Ecom',
    intent: {
      card: IntentType.Authorisation,
      paypal: IntentType.Purchase  // Choose Purchase or Authorisation
    },
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString()
  },
  onGetShopper: () => Promise.resolve({ id: 'shopper-123' }),
  onSuccess: async (result: BaseSubmitResult) => {
    console.log('PayPal payment successful!');
    console.log('System transaction ID:', result.systemTransactionId);
    console.log('Payment method:', result.paymentMethod); // Will be "Paypal"
    
    // CRITICAL: Verify on backend
    await verifyPaymentOnBackend(result);
  },
  onError: (error: BaseSdkException) => {
    console.error('PayPal payment failed:', error);
    alert(`Payment failed: ${error.message}`);
  }
});

// Mount Drop-in
checkoutDropIn.create('checkout-drop-in-container');

Session configuration (backend)

Enable PayPal in your session request:

// BACKEND: Create a session with PayPal enabled
const sessionRequest = {
  merchant: "MERCHANT-1",
  site: "SITE-1",
  sessionTimeout: 120,
  merchantTransactionId: crypto.randomUUID(),
  transactionMethod: {
    intent: {
      paypal: "Purchase"  // or "Authorisation"
    }
  },
  amounts: {
    currencyCode: "USD",
    transactionValue: 99.99
  },
  allowedFundingTypes: {
    wallets: {
      paypal: {
        // PayPal configuration from the Unity Portal will be used
        // You can optionally specify allowedFundingOptions here
        allowedFundingOptions: ["paypal", "paylater", "venmo"]
      }
    }
  },
  allowTransaction: true,
  serviceType: "CheckoutDropIn"
};

Payment flows

Drop-in supports two PayPal payment flows, configured via the intent parameter:

Immediate, single-step payment where funds are captured right away.

import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';

transactionData: {
  currency: 'USD',
  amount: 99.99,
  entryType: 'Ecom',
  intent: {
    card: IntentType.Authorisation,
    paypal: IntentType.Purchase  // Pay Now flow
  },
  merchantTransactionId: crypto.randomUUID(),
  merchantTransactionDate: () => new Date().toISOString()
}

Use this flow for:

  • Digital products
  • Simple orders with immediate fulfillment
  • Subscriptions
  • Donations

Handling responses

PayPal callback data

When a PayPal payment succeeds, your onSuccess callback receives the same standard result as other payment methods:

onSuccess: (result: BaseSubmitResult) => {
  console.log('Payment details:');
  console.log('- System transaction ID:', result.systemTransactionId);
  console.log('- Merchant transaction ID:', result.merchantTransactionId);
  console.log('- Payment method:', result.paymentMethod); // "Paypal"
  
  // Note: Amount, currency, and other transaction details must be retrieved from backend
  // PayPal authentication is handled internally by PayPal
}

Error handling

Handle PayPal-specific errors:

import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';

onError: (error: BaseSdkException) => {
  console.error('Error code:', error.code);
  console.error('Error message:', error.message);
  
  // Handle user cancellation
  if (error.message.includes('cancelled') || error.message.includes('closed')) {
    console.log('User cancelled PayPal payment');
    // Don't show error - user intentionally cancelled
    return;
  }
  
  // Handle PayPal-specific errors based on message content
  if (error.message.includes('insufficient funds')) {
    alert('Insufficient funds in PayPal account. Please add funds or use a different payment method.');
  } else if (error.message.includes('restricted')) {
    alert('Your PayPal account is restricted. Please contact PayPal support.');
  } else if (error.message.includes('declined')) {
    alert('Payment declined by PayPal. Please try a different payment method.');
  } else {
    alert(`PayPal payment failed: ${error.message}`);
  }
}

Common error scenarios

The following table describes common PayPal error scenarios:

ScenarioHow to detectRecommended action
User cancellederror.message contains "cancelled" or "closed"No alert needed - user action was intentional
Insufficient fundserror.message contains "insufficient"Suggest adding funds or using different payment method
Account restrictederror.message contains "restricted"Direct user to contact PayPal support
Payment declinederror.message contains "declined"Suggest trying different payment method

PayPal errors return descriptive messages rather than specific error code constants. Check the error.message property for error details. You can also use the onCancel callback in methodConfig.global to specifically handle user cancellations.

Backend verification

Always verify PayPal payments on your backend to ensure payment success before fulfilling orders:

import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';

onSuccess: async (result: BaseSubmitResult) => {
  // Send to backend for verification
  const verified = await fetch('/api/verify-payment', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      systemTransactionId: result.systemTransactionId,
      merchantTransactionId: result.merchantTransactionId
    })
  }).then(r => r.json());
  
  if (verified.success) {
    window.location.href = `/success?orderId=${verified.orderId}`;
  } else {
    alert('Payment verification failed');
  }
}

Backend verification code

Use the following backend code to verify PayPal transactions via the PXP API:

// BACKEND: Verify PayPal payment
app.post('/api/verify-payment', async (req, res) => {
  const { systemTransactionId, merchantTransactionId } = req.body;
  
  try {
    // Query the PXP API to get transaction details
    const txnPath = `api/v1/transactions/${systemTransactionId}`;
    const { authHeader, requestId } = createAuthHeader(
      txnPath,
      '',
      process.env.PXP_TOKEN_ID,
      process.env.PXP_TOKEN_VALUE
    );
    
    const transaction = await fetch(
      `https://api-services.pxp.io/${txnPath}`,
      {
        headers: {
          'X-Client-Id': process.env.PXP_CLIENT_ID,
          'X-Request-Id': requestId,
          'Authorization': authHeader
        }
      }
    ).then(r => r.json());
    
    // Verify transaction state
    if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
      return res.json({ success: false, error: 'Transaction not successful' });
    }
    
    // Verify merchant transaction ID matches
    if (transaction.merchantTransactionId !== merchantTransactionId) {
      return res.json({ success: false, error: 'Transaction ID mismatch' });
    }
    
    // Verify amount matches expected amount from your order records
    const order = await getOrderByMerchantTransactionId(merchantTransactionId);
    const txnAmount = transaction.amounts?.transactionValue || transaction.amount || 0;
    if (Math.abs(txnAmount - order.amount) > 0.01) {
      return res.json({ success: false, error: 'Amount mismatch' });
    }
    
    // PayPal payments show as PayPal funding type
    const fundingType = transaction.fundingData?.fundingType || 
                       transaction.fundingType || 
                       'Unknown';
    if (fundingType !== 'PayPal') {
      return res.json({ success: false, error: 'Invalid funding type' });
    }
    
    // Fulfill order
    const orderId = await fulfillOrder(transaction);
    
    return res.json({ success: true, orderId });
    
  } catch (error) {
    console.error('Verification error:', error);
    return res.json({ success: false, error: 'Verification failed' });
  }
});

Advanced PayPal flows

PayPal vaulting (one-click payment)

PayPal vaulting allows returning customers to pay with one click by saving their PayPal account. When enabled, customers who have previously connected their PayPal account can skip the PayPal login flow entirely.

How it works

  1. The customer pays with PayPal and agrees to save their account.
  2. PayPal vaults the account and returns a vault ID.
  3. On return visit, onGetShopper provides the shopper ID.
  4. Drop-in enables one-click PayPal payment (no popup).
  5. The customer clicks "Pay with PayPal" and payment completes instantly.

Enable PayPal vaulting

Implement onGetShopper to enable vaulting:

import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';

// Get session from backend (with PayPal enabled)
const response = await fetch('/api/create-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});

const result = await response.json();

if (!result.success || !result.data) {
  console.error('Failed to create session:', result.error);
  throw new Error('Session creation failed');
}

const sessionData = result.data;

const checkoutDropIn = CheckoutDropIn.initialize({
  environment: 'test',
  session: sessionData,
  ownerId: 'MERCHANT-1',
  ownerType: 'MerchantGroup',
  transactionData: {
    currency: 'USD',
    amount: 99.99,
    entryType: 'Ecom',
    intent: {
      card: IntentType.Purchase,
      paypal: IntentType.Purchase
    },
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString()
  },
  // REQUIRED: Provide shopper ID to enable PayPal vaulting
  onGetShopper: async () => {
    const user = await getCurrentUser();
    return { id: user.shopperId }; // e.g., { id: 'shopper-123' }
  },
  onSuccess: async (result: BaseSubmitResult) => {
    await verifyPaymentOnBackend(result);
    window.location.href = '/success';
  },
  onError: (error: BaseSdkException) => {
    console.error('PayPal payment failed:', error);
    alert(`Payment failed: ${error.message}`);
  }
});

checkoutDropIn.create('checkout-drop-in-container');

When onGetShopper returns a shopper ID, the SDK automatically:

  • Fetches the PayPal user ID token from the PXP API.
  • Enables one-click payment for vaulted PayPal accounts.
  • Handles vault setup during the first payment.

PayPal vaulting configuration

You can configure PayPal vaulting behaviour using methodConfig.paypal.enableOneClickPayment:

CheckoutDropIn.initialize({
  // ... other config
  methodConfig: {
    paypal: {
      enableOneClickPayment: true  // Enable PayPal vaulting
    }
  }
});