Implement callbacks to customise your PayPal payment flow for Web.
Components emit events based on user interaction or validation. You can use these to implement callback functions, which allow you to inject your own business logic and user experience customisations into the payment flow at critical moments. They ensure that while the SDK handles the complex technical aspects of payment processing, you retain full control over the customer experience and can seamlessly integrate payments into your broader business workflows and systems.
Callbacks enable you to:
- Validate business rules before payments proceed.
- Display custom error, failure, or success messages.
- Tailor user interfaces to match your brand's look and feel.
- Integrate with your own systems for fraud detection or customer management.
- Control exactly how your customers experience both successful and failed transactions.
- Handle PayPal-specific requirements like shipping calculations and address validation.
- Manage different funding sources (PayPal, Pay Later, Venmo).
- Customise the checkout experience based on customer preferences.
The PayPal component requires SDK initialisation callbacks to gather shopper and shipping information during transaction processing:
onGetShopper: Required - Called to retrieve current shopper data (ID, email, name, etc.) for transaction submission to the backend.onGetShippingAddress: Required when shipping is needed - Called to get shipping address information for the transaction.
These callbacks ensure the PayPal component always uses the latest customer data from your application state, forms, or APIs when processing payments.
All PayPal-specific events are optional and can be mixed and matched based on your business needs.
The SDK initialisation callbacks (onGetShopper, onGetShippingAddress) work alongside PayPal-specific callbacks to provide a complete payment experience. SDK callbacks handle backend transaction data, while PayPal callbacks handle the PayPal checkout flow interactions.
This callback is triggered when the buyer approves the payment in the PayPal checkout flow. This is where you capture the funds and complete the transaction.
You can use it to:
- Capture the approved payment and complete the transaction.
- Update inventory and order status in your system.
- Send order confirmation emails to customers.
- Record successful transactions for business intelligence.
| Event data | Description |
|---|---|
dataobject | The payment approval data from PayPal. |
data.orderIDstring | The PayPal order ID for the approved payment. |
data.paymentIDstring | The PayPal payment ID (legacy v1/v2 API). |
data.paymentSourcestring | The funding source used (paypal, paylater, venmo). |
data.facilityIdstring | The merchant facility ID. |
actionsobject | PayPal actions object containing helper methods. |
actions.capturefunction | Method to capture the payment. |
actions.getfunction | Method to get order details. |
const paypalComponent = sdk.create('paypal-button', {
onApprove: async (data, actions) => {
console.log('PayPal payment approved:', data);
try {
// Capture the payment using PayPal actions
const captureResult = await actions.capture();
console.log('Payment captured successfully:', captureResult);
// Extract important transaction details
const transactionId = captureResult.id;
const payerEmail = captureResult.payer.email_address;
const amount = captureResult.purchase_units[0].amount.value;
const currency = captureResult.purchase_units[0].amount.currency_code;
// Update your system with the successful payment
await updateOrderStatus({
orderId: data.orderID,
transactionId: transactionId,
status: 'completed',
paymentMethod: 'paypal',
fundingSource: data.paymentSource,
amount: amount,
currency: currency
});
// Update inventory
await updateInventory(data.orderID);
// Send confirmation email
await sendConfirmationEmail(payerEmail, {
orderId: data.orderID,
transactionId: transactionId,
amount: amount,
currency: currency
});
// Track successful payment
trackEvent('paypal-payment-completed', {
orderId: data.orderID,
transactionId: transactionId,
fundingSource: data.paymentSource,
amount: amount,
currency: currency,
timestamp: new Date().toISOString()
});
// Redirect to success page
window.location.href = `/payment-success?order=${data.orderID}&txn=${transactionId}`;
} catch (error) {
console.error('Payment capture failed:', error);
// Handle capture failure
showErrorMessage('Payment processing failed. Please try again or contact support.');
// Log the error for debugging
logError('paypal-capture-failed', {
orderId: data.orderID,
error: error.message,
timestamp: new Date().toISOString()
});
}
}
});This callback is triggered when the buyer cancels the payment flow in the PayPal popup or redirects back without completing the payment.
You can use it to:
- Track cancellation rates for conversion optimisation.
- Show helpful messages or alternative payment options.
- Save the customer's cart for later completion.
- Trigger email campaigns for abandoned checkouts.
| Parameter | Description |
|---|---|
dataobject | The cancellation data from PayPal. |
data.orderIDstring | The PayPal order ID that was cancelled. |
data.paymentIDstring | The PayPal payment ID (if available). |
const paypalComponent = sdk.create('paypal-button', {
onCancel: (data) => {
console.log('PayPal payment cancelled:', data);
// Track cancellation for analytics
trackEvent('paypal-payment-cancelled', {
orderId: data.orderID,
timestamp: new Date().toISOString(),
cartValue: getCurrentCartValue(),
stage: 'paypal_checkout'
});
// Preserve cart for later
saveCartForLater();
// Show helpful message
showMessage('No worries! Your items are saved. You can complete your purchase anytime.', 'info');
// Offer alternatives after a short delay
setTimeout(() => {
showAlternativePaymentOptions();
}, 2000);
// Optional: Schedule abandoned cart email
scheduleAbandonedCartEmail(customerEmail, 30); // 30 minutes delay
}
});This callback is triggered when an error occurs during the PayPal payment process, such as network issues, invalid configuration, or payment processing failures.
You can use it to:
- Log errors for debugging and monitoring.
- Display user-friendly error messages.
- Offer alternative payment methods.
- Implement automatic retry for transient errors.
| Parameter | Description |
|---|---|
errorError | The error object containing details about what went wrong. |
error.messagestring | A human-readable error description. |
error.namestring | The error type or name. |
error.stackstring | The stack trace for debugging. |
const paypalComponent = sdk.create('paypal-button', {
onError: (error) => {
console.error('PayPal payment error:', error);
// Log error for debugging
logError('paypal-payment-error', {
message: error.message,
name: error.name,
stack: error.stack,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
});
// Handle different error types
if (error.message.includes('INSTRUMENT_DECLINED')) {
showUserMessage('Your PayPal payment was declined. Please try a different payment method or contact PayPal support.');
} else if (error.message.includes('PAYER_ACTION_REQUIRED')) {
showUserMessage('Additional action required in PayPal. Please try again or complete any pending actions in your PayPal account.');
} else if (error.message.includes('UNPROCESSABLE_ENTITY')) {
showUserMessage('There was an issue processing your payment. Please check your payment details and try again.');
} else if (error.message.includes('Network')) {
showUserMessage('Network error. Please check your connection and try again.');
// Offer retry option for network errors
showRetryButton();
} else {
showUserMessage('Payment failed. Please try again or use a different payment method.');
}
// Show alternative payment methods
showAlternativePaymentMethods();
// Notify support team for critical errors
if (error.message.includes('INTERNAL_ERROR')) {
notifySupport('PayPal internal error occurred', error);
}
}
});This callback is triggered when the PayPal order is successfully created but before the customer approves the payment.
You can use it to:
- Store the order ID for tracking purposes.
- Update order status in your system.
- Log order creation events for analytics.
- Validate order creation success.
| Parameter | Description |
|---|---|
dataobject | The order creation response from PXP's backend. |
data.providerTransactionIdstring | The PayPal order ID that was created. |
data.transactionIdstring | The PXP transaction ID. |
data.correlationIdstring | The correlation ID for tracking. |
const paypalComponent = sdk.create('paypal-button', {
onOrderCreated: (data) => {
console.log('PayPal order created successfully:', data);
// Store order information
storeOrderInformation({
paypalOrderId: data.providerTransactionId,
pxpTransactionId: data.transactionId,
correlationId: data.correlationId,
status: 'created',
createdAt: new Date().toISOString()
});
// Update UI to show order created state
updateOrderStatus('Order created, awaiting payment approval...');
// Track order creation
trackEvent('paypal-order-created', {
paypalOrderId: data.providerTransactionId,
pxpTransactionId: data.transactionId,
correlationId: data.correlationId,
timestamp: new Date().toISOString()
});
// Optional: Start order timeout timer
startOrderTimeoutTimer(data.providerTransactionId, 15); // 15 minute timeout
}
});This callback is triggered when the customer changes their shipping address in the PayPal checkout flow. Use this to calculate shipping costs and validate delivery availability.
You can use it to:
- Calculate shipping costs based on the new address.
- Validate if delivery is available to the location.
- Update tax rates based on the shipping destination.
- Check shipping restrictions for specific products.
| Parameter | Description |
|---|---|
dataobject | The shipping address change data. |
data.shipping_addressobject | The new shipping address information. |
data.shipping_address.country_codestring | The country code of the shipping address. |
data.shipping_address.statestring | The state or province. |
data.shipping_address.citystring | The city name. |
data.shipping_address.postal_codestring | The postal or ZIP code. |
actionsobject | PayPal actions object for updating the order. |
actions.resolvefunction | Method to approve the address change with updated order details. |
actions.rejectfunction | Method to reject the address change. |
const paypalComponent = sdk.create('paypal-button', {
onShippingAddressChange: async (data, actions) => {
console.log('PayPal shipping address changed:', data.shipping_address);
try {
const shippingAddress = data.shipping_address;
// Validate if we can ship to this location
const canShip = await validateShippingAddress(shippingAddress);
if (!canShip) {
return actions.reject();
}
// Calculate new shipping cost and tax
const shippingCost = await calculateShipping(shippingAddress);
const taxAmount = await calculateTax(shippingAddress, baseAmount);
const newTotal = baseAmount + taxAmount + shippingCost;
// Get available shipping methods for this address
const shippingOptions = await getShippingOptions(shippingAddress);
// Update the order with new totals and shipping options
return actions.resolve({
purchase_units: [{
amount: {
currency_code: 'USD',
value: newTotal.toFixed(2),
breakdown: {
item_total: {
currency_code: 'USD',
value: baseAmount.toFixed(2)
},
shipping: {
currency_code: 'USD',
value: shippingCost.toFixed(2)
},
tax_total: {
currency_code: 'USD',
value: taxAmount.toFixed(2)
}
}
},
shipping: {
options: shippingOptions.map(option => ({
id: option.id,
label: option.label,
type: option.type,
amount: {
currency_code: 'USD',
value: option.cost.toFixed(2)
},
selected: option.selected
}))
}
}]
});
} catch (error) {
console.error('Shipping calculation failed:', error);
return actions.reject();
}
}
});This callback is triggered when the customer selects a different shipping option in the PayPal checkout flow.
You can use it to:
- Update the total cost based on the shipping method selection.
- Show updated delivery dates for the selected method.
- Apply shipping-specific business rules or discounts.
- Track popular shipping method preferences.
| Parameter | Description |
|---|---|
dataobject | The shipping option change data. |
data.selected_shipping_optionobject | The selected shipping option. |
data.selected_shipping_option.idstring | The shipping option identifier. |
data.selected_shipping_option.labelstring | The shipping option display name. |
data.selected_shipping_option.typestring | The shipping option type. |
data.selected_shipping_option.amount.valuestring | The shipping cost. |
actionsobject | PayPal actions object for updating the order. |
const paypalComponent = sdk.create('paypal-button', {
onShippingOptionsChange: async (data, actions) => {
console.log('PayPal shipping option changed:', data.selected_shipping_option);
const selectedOption = data.selected_shipping_option;
const baseAmount = 100.00;
const taxAmount = 10.00;
const shippingCost = parseFloat(selectedOption.amount.value);
// Add insurance for expedited shipping
let insurance = 0;
if (selectedOption.id === 'expedited' && baseAmount > 50) {
insurance = 2.99;
}
// Calculate carbon offset for eco-friendly shipping
const carbonOffset = selectedOption.id === 'standard' ? 0.50 : 0;
const newTotal = baseAmount + taxAmount + shippingCost + insurance + carbonOffset;
// Track shipping method selection
trackEvent('paypal-shipping-method-selected', {
shippingMethod: selectedOption.id,
shippingCost: shippingCost,
orderValue: baseAmount,
timestamp: new Date().toISOString()
});
// Update order totals
return actions.resolve({
purchase_units: [{
amount: {
currency_code: 'USD',
value: newTotal.toFixed(2),
breakdown: {
item_total: {
currency_code: 'USD',
value: baseAmount.toFixed(2)
},
shipping: {
currency_code: 'USD',
value: shippingCost.toFixed(2)
},
tax_total: {
currency_code: 'USD',
value: taxAmount.toFixed(2)
},
...(insurance > 0 && {
insurance: {
currency_code: 'USD',
value: insurance.toFixed(2)
}
})
}
}
}]
});
}
});This callback is triggered when the PayPal button is initialised and ready for interaction.
You can use it to:
- Control when the PayPal button becomes available for clicks.
- Implement custom validation before allowing payment.
- Show loading states or preparation messages.
- Enable or disable the button based on form validation.
| Parameter | Description |
|---|---|
dataobject | The initialisation data from PayPal. |
actionsobject | PayPal actions object for controlling button state. |
actions.enablefunction | Method to enable the PayPal button. |
actions.disablefunction | Method to disable the PayPal button. |
const paypalComponent = sdk.create('paypal-button', {
onInit: (data, actions) => {
console.log('PayPal button initialised:', data);
// Initially disable the button
actions.disable();
// Enable button when form is valid
function validateForm() {
const email = document.getElementById('email')?.value;
const terms = document.getElementById('terms')?.checked;
if (email && terms && email.includes('@')) {
actions.enable();
} else {
actions.disable();
}
}
// Set up form validation listeners
document.getElementById('email')?.addEventListener('input', validateForm);
document.getElementById('terms')?.addEventListener('change', validateForm);
// Initial validation
validateForm();
}
});This callback is triggered when the PayPal button is clicked, before the PayPal checkout flow begins.
You can use it to:
- Validate collected shipping address and shopper details before proceeding to PayPal.
- Perform final validation before checkout.
- Track button click events for analytics.
- Show loading states or checkout preparation messages.
- Prevent checkout if conditions are not met.
The SDK will automatically collect shopper and shipping address data via the onGetShopper and onGetShippingAddress callbacks when creating the PayPal order. Use the onClick callback to validate this data before the user is redirected to PayPal.
| Parameter | Description |
|---|---|
dataobject | The click event data from PayPal. |
actionsobject | PayPal actions object for controlling the flow. |
actions.resolvefunction | Method to continue with the checkout flow. |
actions.rejectfunction | Method to prevent the checkout flow. |
const paypalComponent = sdk.create('paypal-button', {
onClick: async (data, actions) => {
console.log('PayPal button clicked:', data);
// Track button click
trackEvent('paypal-button-clicked', {
timestamp: new Date().toISOString(),
cartValue: getCurrentCartValue()
});
// Validate collected shipping address and shopper details before proceeding
try {
// Collect current shopper data from your form/state
const shopperData = {
id: getCurrentShopperId(),
email: getCustomerEmail(),
firstName: getCustomerFirstName(),
lastName: getCustomerLastName(),
phoneNumber: getCustomerPhone()
};
// Collect current shipping address from your form/state
const shippingAddress = {
address: getShippingAddress(),
city: getShippingCity(),
state: getShippingState(),
postalCode: getShippingPostalCode(),
countryCode: getShippingCountryCode()
};
// Validate shopper data completeness
if (!shopperData.id || !shopperData.email) {
showError('Please complete customer information before proceeding.');
return actions.reject();
}
// Validate shipping address when required
if (requiresShipping() && (!shippingAddress.address || !shippingAddress.city || !shippingAddress.postalCode)) {
showError('Please complete shipping address before proceeding.');
return actions.reject();
}
// Additional business validations
const isFormValid = validateCheckoutForm();
const hasInventory = await checkInventoryAvailability();
const isCustomerEligible = await checkCustomerEligibility();
if (!isFormValid) {
showError('Please complete all required fields before proceeding.');
return actions.reject();
}
if (!hasInventory) {
showError('Some items in your cart are no longer available.');
return actions.reject();
}
if (!isCustomerEligible) {
showError('PayPal is not available for your location.');
return actions.reject();
}
// All validations passed - show loading state and proceed
showCheckoutLoader();
return actions.resolve();
} catch (error) {
console.error('Validation failed:', error);
showError('Please verify your information and try again.');
return actions.reject();
}
}
});This callback is triggered when there's an error during the order creation or submission process on the PXP side.
You can use it to:
- Handle PXP-specific errors and validation failures.
- Display detailed error messages to users.
- Log submission errors for debugging.
- Implement retry logic for failed submissions.
| Parameter | Description |
|---|---|
errorBaseSdkException | The SDK exception containing error details. |
error.messagestring | The error message. |
error.errorCodestring | The specific error code. |
const paypalComponent = sdk.create('paypal-button', {
onSubmitError: (error) => {
console.error('PayPal submission error:', error);
// Log error for debugging
logError('paypal-submission-error', {
message: error.message,
errorCode: error.errorCode,
timestamp: new Date().toISOString()
});
// Handle specific error types
switch (error.errorCode) {
case 'VALIDATION_ERROR':
showError('Please check your payment details and try again.');
break;
case 'INSUFFICIENT_FUNDS':
showError('Insufficient funds in your PayPal account. Please add funds or use a different payment method.');
break;
case 'MERCHANT_NOT_ELIGIBLE':
showError('PayPal payments are temporarily unavailable. Please try a different payment method.');
break;
default:
showError('Payment processing failed. Please try again or contact support.');
}
// Offer alternative payment methods
showAlternativePaymentMethods();
}
});This callback is triggered when the PayPal SDK script has been successfully loaded and is ready to use.
You can use it to:
- Initialise PayPal-related features that depend on the SDK.
- Show PayPal buttons that were hidden during loading.
- Track script loading performance.
- Set up PayPal-specific configurations.
const paypalComponent = sdk.create('paypal-button', {
onScriptLoaded: () => {
console.log('PayPal SDK script loaded successfully');
// Track script loading
trackEvent('paypal-script-loaded', {
timestamp: new Date().toISOString(),
loadTime: performance.now()
});
// Show PayPal buttons that were hidden during loading
document.querySelectorAll('.paypal-button-container').forEach(container => {
container.style.display = 'block';
});
// Hide loading spinner
hidePayPalLoadingSpinner();
// Initialise PayPal messaging
if (window.paypal && window.paypal.Messages) {
window.paypal.Messages({
amount: getCurrentCartValue(),
placement: 'cart',
style: {
layout: 'text',
logo: {
type: 'primary'
}
}
}).render('#paypal-messages');
}
}
});When using the setOfButtons render type, you can handle different funding sources:
const paypalComponent = sdk.create('paypal-button', {
renderType: 'setOfButtons',
fundingSources: ['paypal', 'paylater', 'venmo'],
onClick: (data, actions) => {
// Track which funding source was clicked
trackEvent('paypal-funding-source-clicked', {
fundingSource: data.fundingSource,
timestamp: new Date().toISOString()
});
// Apply different logic based on funding source
if (data.fundingSource === 'paylater') {
// Special handling for Pay Later
const isEligibleForPayLater = checkPayLaterEligibility();
if (!isEligibleForPayLater) {
showError('Pay Later is not available for this purchase.');
return actions.reject();
}
}
return actions.resolve();
},
onApprove: async (data, actions) => {
// Handle approval differently based on funding source
const fundingSource = data.paymentSource || 'paypal';
trackEvent('paypal-payment-approved', {
fundingSource: fundingSource,
orderId: data.orderID,
timestamp: new Date().toISOString()
});
// Proceed with capture
return await actions.capture();
}
});Comprehensive error handling for PayPal payments:
const paypalComponent = sdk.create('paypal-button', {
onError: (error) => {
const errorInfo = {
message: error.message,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
};
// Log to monitoring service
logError('paypal-error', errorInfo);
// Determine if error is retryable
const retryableErrors = [
'NETWORK_ERROR',
'TIMEOUT',
'TEMPORARY_FAILURE'
];
const isRetryable = retryableErrors.some(retryableError =>
error.message.includes(retryableError)
);
if (isRetryable) {
showRetryButton(() => {
// Retry the PayPal initialisation
location.reload();
});
} else {
// Show alternative payment methods
showAlternativePaymentMethods();
}
// Send error metrics to analytics
trackEvent('paypal-error', {
errorType: error.name,
errorMessage: error.message,
isRetryable: isRetryable
});
}
});