Pay now flow

Capture funds instantly upon customer approval.

Overview

The pay now flow is designed for immediate payment capture, making it ideal for digital products, services, or when instant payment confirmation is required.

Payment flow

The PayPal pay now flow consists of five key steps for immediate payment processing.

Step 1: Submission

The customer clicks the PayPal button, which triggers the payment flow. The SDK validates the PayPal configuration and transaction data before proceeding to order creation. If validation fails, onError is triggered.

Step 2: Order creation

The SDK creates a PayPal order using the provided transaction details. This involves sending the payment amount, currency, merchant information, and any additional order details to PayPal's API. If order creation fails, onError is triggered.

Step 3: PayPal approval

The customer is redirected to PayPal where they can log in and approve the payment.

This step has three associated callbacks:

  • onApprove: Proceeds with payment capture if the customer successfully approves the payment.
  • onCancel: Cancels the transaction if the customer cancels the payment.
  • onError: Receives error data if any error occurs during the approval process.

PayPal handles all authentication and payment method selection within their secure environment.

Step 4: Payment capture

Once the customer approves the payment, the SDK processes the payment immediately and automatically captures the funds. This happens within the onApprove callback handler, where you process the capture and handle success/failure.

Step 5: Payment result

You receive the final payment result from PayPal and the onApprove callback completes processing. The transaction is either completed successfully with payment confirmation, or fails with specific error details.

Implementation

Before you start

To use PayPal pay now payments in your application:

  1. Ensure you have a valid PayPal Business account with API credentials.
  2. Configure your PayPal merchant account to accept the currencies you need.
  3. Set up your merchant configuration in the Unity Portal (API credentials, payment methods, risk settings).

Step 1: Configure your SDK

Set up your sdkConfig with transaction information for PayPal payments.

const sdkConfig = {
  transactionData: {
    amount: 2500,
    currency: 'USD',
    entryType: 'Ecom',
    intent: 'Create',
    merchantTransactionId: 'order-' + Date.now(),
    merchantTransactionDate: () => new Date().toISOString(),
    // Optional shopper data
    shopper: {
      email: '[email protected]',
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  // Your other SDK configuration
};

Step 2: Implement callbacks

Implement the required callbacks for PayPal pay now payments.

const paypalComponent = pxpSdk.create('paypal-button', {
  // Required PayPal configuration
  payeeEmailAddress: '[email protected]',
  paymentDescription: 'Product purchase',
  shippingPreference: 'NO_SHIPPING',
  userAction: 'PAY_NOW',
  renderType: 'standalone',
  fundingSources: 'paypal',

  // REQUIRED: Handle successful payment approval
  onApprove: async (data, actions) => {
    console.log('PayPal payment approved:', data);
    
    try {
      // Process the payment on your backend
      const result = await fetch('/api/capture-paypal-payment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          orderID: data.orderID,
          payerID: data.payerID,
          merchantTransactionId: sdkConfig.transactionData.merchantTransactionId
        })
      });
      
      const response = await result.json();
      
      if (response.success) {
        console.log('Payment captured successfully');
        window.location.href = '/payment-success?orderID=' + data.orderID;
      } else {
        throw new Error(response.error || 'Payment capture failed');
      }
      
    } catch (error) {
      console.error('Payment processing error:', error);
      showError('Payment failed. Please try again.');
    }
  },

  // REQUIRED: Handle payment errors
  onError: (error) => {
    console.error('PayPal payment error:', error);
    showError('Payment failed. Please try again.');
  },

  // OPTIONAL: Handle payment cancellation
  onCancel: (data) => {
    console.log('PayPal payment cancelled:', data);
    showMessage('Payment was cancelled. You can try again anytime.');
  }
});

Step 3: Handle common scenarios

Amount-based processing

Use different processing logic based on transaction amounts.

const paypalComponent = pxpSdk.create('paypal-button', {
  onApprove: async (data, actions) => {
    const amount = sdkConfig.transactionData.amount;
    
    // Add additional verification for high-value transactions
    if (amount > 100) { // Over 100
      const confirmed = await showConfirmationDialog(
        `Confirm payment of $${(amount / 100).toFixed(2)}?`
      );
      
      if (!confirmed) {
        showMessage('Payment cancelled by user.');
        return;
      }
    }
    
    // Process the payment
    await processPayPalPayment(data);
  }
});

Customer type handling

Handle different customer types with varying processing requirements.

const getCustomerProcessingOptions = (customerType) => {
  switch (customerType) {
    case 'new':
      return { verification: 'enhanced', emailConfirmation: true };
    case 'returning':
      return { verification: 'standard', emailConfirmation: false };
    case 'vip':
      return { verification: 'minimal', fastProcessing: true };
    default:
      return { verification: 'standard', emailConfirmation: true };
  }
};

const paypalComponent = pxpSdk.create('paypal-button', {
  onApprove: async (data, actions) => {
    const processingOptions = getCustomerProcessingOptions('returning');
    
    await processPayPalPayment(data, {
      ...processingOptions,
      customerType: 'returning',
      timestamp: new Date().toISOString()
    });
  }
});

Step 4: Handle errors

Implement comprehensive error handling for PayPal payments.

const paypalComponent = pxpSdk.create('paypal-button', {
  onError: (error) => {
    console.error('PayPal error:', error);
    
    // Handle specific PayPal error types
    if (error.name === 'VALIDATION_ERROR') {
      showError('Payment details validation failed. Please try again.');
    } else if (error.name === 'INSTRUMENT_DECLINED') {
      showError('Your PayPal payment method was declined. Please try a different method.');
    } else if (error.name === 'PAYER_ACTION_REQUIRED') {
      showError('Additional action required in PayPal. Please complete the payment process.');
    } else if (error.name === 'UNPROCESSABLE_ENTITY') {
      showError('Payment cannot be processed. Please contact support.');
    } else {
      showError('Payment failed. Please try again or contact support.');
    }
  },

  onApprove: async (data, actions) => {
    try {
      await processPayPalPayment(data);
    } catch (error) {
      // Handle backend processing errors
      if (error.status === 422) {
        showError('Payment details could not be verified. Please try again.');
      } else if (error.status === 409) {
        showError('This payment has already been processed.');
      } else if (error.status >= 500) {
        showError('Payment system temporarily unavailable. Please try again later.');
      } else {
        showError('Payment processing failed. Please try again.');
      }
    }
  }
});

Example

The following example shows a complete PayPal pay now implementation.

const paypalComponent = pxpSdk.create('paypal-button', {
  // PayPal configuration
  payeeEmailAddress: '[email protected]',
  paymentDescription: 'Product Purchase - $25.00',
  shippingPreference: 'NO_SHIPPING',
  userAction: 'PAY_NOW',
  renderType: 'standalone',
  fundingSources: 'paypal',
  
  // Styling
  style: {
    layout: 'vertical',
    color: 'gold',
    shape: 'rect',
    label: 'paypal'
  },

  // Step 1: Handle payment approval
  onApprove: async (data, actions) => {
    console.log('Processing PayPal payment');
    
    // Show loading state
    showLoadingSpinner();
    
    try {
      // Log transaction details
      console.log(`Order ID: ${data.orderID}`);
      console.log(`Payer ID: ${data.payerID}`);
      console.log(`Amount: $${(sdkConfig.transactionData.amount / 100).toFixed(2)}`);
      
      // Process payment on backend
      const response = await fetch('/api/paypal/capture', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          orderID: data.orderID,
          payerID: data.payerID,
          merchantTransactionId: sdkConfig.transactionData.merchantTransactionId,
          amount: sdkConfig.transactionData.amount,
          currency: sdkConfig.transactionData.currency
        })
      });
      
      const result = await response.json();
      
      if (result.success) {
        // Success - store transaction and redirect
        console.log('PayPal payment completed successfully');
        sessionStorage.setItem('paymentSuccess', JSON.stringify({
          orderID: data.orderID,
          transactionId: result.transactionId,
          amount: result.amount
        }));
        window.location.href = `/payment-success?orderID=${data.orderID}`;
      } else {
        throw new Error(result.error || 'Payment capture failed');
      }
      
    } catch (error) {
      console.error('PayPal payment processing failed:', error);
      hideLoadingSpinner();
      showError('Payment failed: ' + (error.message || 'Please try again'));
    }
  },

  // Step 2: Handle cancellation
  onCancel: (data) => {
    console.log('PayPal payment cancelled by user');
    showMessage('Payment was cancelled. Your order is still available.');
  },

  // Step 3: Handle errors
  onError: (error) => {
    console.error('PayPal payment error:', error);
    hideLoadingSpinner();
    showError('Payment failed. Please try again.');
  }
});

// Mount the component
paypalComponent.mount('paypal-button-container');

Callback data

This section describes the data received by the different callbacks as part of the PayPal pay now flow.

onApprove

The onApprove callback receives payment approval data when the customer successfully approves the payment in PayPal.

Payment approval data

The approval data includes the PayPal order ID and payer information needed to capture the payment.

{
  orderID: "7YH53119ML8957234",
  payerID: "ABCDEFGHIJKLM",
  paymentID: "PAYID-ABCDEFG",
  billingToken: null,
  facilitatorAccessToken: "A21AAFExi..."
}
ParameterDescription
orderID
stringrequired
The unique PayPal order ID that identifies this payment.
payerID
stringrequired
The unique PayPal payer ID that identifies the customer who approved the payment.
paymentID
string
The PayPal payment ID for this transaction.
billingToken
string or noll
The billing agreement token if applicable, otherwise null.
facilitatorAccessToken
string
The PayPal facilitator access token for processing the payment.

Here's an example of what to do with this data:

onApprove: async (data, actions) => {
  console.log('PayPal payment approved:', data);
  
  try {
    // Capture the payment immediately for pay now flow
    const captureResponse = await fetch('/api/paypal/capture', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        orderID: data.orderID,
        payerID: data.payerID,
        // Add merchant context
        merchantTransactionId: generateTransactionId(),
        timestamp: new Date().toISOString(),
        // Add risk/fraud data
        browserInfo: {
          userAgent: navigator.userAgent,
          language: navigator.language,
          timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
        }
      })
    });
    
    const result = await captureResponse.json();
    
    if (result.status === 'COMPLETED') {
      // Payment captured successfully
      console.log('Payment captured:', result.id);
      
      // Store transaction record
      await storeTransaction({
        paypalOrderId: data.orderID,
        paypalPayerId: data.payerID,
        transactionId: result.id,
        amount: result.purchase_units[0].amount.value,
        currency: result.purchase_units[0].amount.currency_code,
        status: 'completed',
        timestamp: new Date().toISOString()
      });
      
      // Redirect to success
      window.location.href = `/success?orderID=${data.orderID}`;
      
    } else {
      throw new Error(`Payment capture failed: ${result.status}`);
    }
    
  } catch (error) {
    console.error('Payment capture error:', error);
    showError('Payment processing failed. Please contact support.');
  }
}

onError

The onError callback receives error information when PayPal payments fail.

Error data

PayPal errors include specific error names and details to help with troubleshooting.

{
  name: "VALIDATION_ERROR",
  message: "Invalid payment method",
  details: [{
    issue: "INSTRUMENT_DECLINED",
    description: "The instrument presented was either declined by the processor or bank, or it can't be used for this payment."
  }]
}

Parameter

Description

name
string

The error name.

Possible values:

  • VALIDATION_ERROR
  • INSTRUMENT_DECLINED
  • PAYER_ACTION_REQUIRED
  • UNPROCESSABLE_ENTITY

message
string

A human-readable error message.

details
array of string

An array of detailed error information.

details[].issue
string

The specific issue code from PayPal.

details[].description
string

A detailed description of the issue.

Here's an example of how to handle PayPal errors:

onError: (error) => {
  console.error('PayPal payment error:', error);
  
  // Handle specific error types
  switch (error.name) {
    case 'VALIDATION_ERROR':
      showError('Payment information is invalid. Please try again.');
      logError('PayPal validation error', error);
      break;
      
    case 'INSTRUMENT_DECLINED':
      showError('Your PayPal payment method was declined. Please try a different payment method.');
      break;
      
    case 'PAYER_ACTION_REQUIRED':
      showError('Additional verification required. Please complete the process in PayPal.');
      break;
      
    case 'UNPROCESSABLE_ENTITY':
      showError('This payment cannot be processed. Please contact support.');
      logError('PayPal unprocessable entity', error);
      break;
      
    case 'INTERNAL_SERVICE_ERROR':
      showError('PayPal service temporarily unavailable. Please try again later.');
      break;
      
    default:
      showError('Payment failed. Please try again or contact support.');
      logError('Unknown PayPal error', error);
  }
  
  // Log error details for monitoring
  logPaymentError({
    errorName: error.name,
    errorMessage: error.message,
    errorDetails: error.details,
    timestamp: new Date().toISOString(),
    paymentMethod: 'paypal'
  });
}

onCancel

The onCancel callback receives data when the customer cancels the PayPal payment process.

Cancellation data

The cancellation data includes the order ID and reason for the cancellation.

{
  orderID: "7YH53119ML8957234",
  reason: "user_cancelled"
}
ParameterDescription
orderId
string
required
The PayPal order ID that was being processed when the cancellation happened.
reason
string
The reason for cancellation. Typically, this is user_cancelled.

Here's an example of how to handle cancellations:

onCancel: (data) => {
  console.log('PayPal payment cancelled:', data);
  
  // Log cancellation for analytics
  logPaymentCancellation({
    orderID: data.orderID,
    reason: data.reason || 'user_cancelled',
    timestamp: new Date().toISOString(),
    paymentMethod: 'paypal'
  });
  
  // Show user-friendly message
  showMessage('Payment was cancelled. Your cart items are still saved.');
  
  // Optional: Offer alternative payment methods
  showAlternativePaymentOptions();
}