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:
- Ensure you have a valid PayPal Business account with API credentials.
- Configure your PayPal merchant account to accept the currencies you need.
- 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..."
}
Parameter | Description |
---|---|
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 |
---|---|
| The error name.
|
| A human-readable error message. |
| An array of detailed error information. |
| The specific issue code from PayPal. |
| 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"
}
Parameter | Description |
---|---|
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();
}
Updated 13 days ago