Send payouts to returning customers using stored wallet details.
The withdrawal flow is designed for returning customers whose wallet details you already have stored. This provides a faster, streamlined experience — the customer simply reviews the payout details and confirms, without needing to log in again.
This flow uses the receiver and submission components together, displaying the stored wallet information and providing a "Withdraw" button.
The withdrawal flow consists of five key steps for returning customer payouts.
The customer sees their stored PayPal email or Venmo handle displayed alongside the payout amount. The receiver component shows the wallet destination, optionally masked for privacy.
The customer clicks the "Withdraw with PayPal" or "Withdraw with Venmo" button. The SDK validates the payout configuration and stored wallet details before proceeding. If validation fails, onError is triggered.
The onPrePayoutSubmit callback is triggered, giving you the opportunity to show a confirmation dialog or perform additional validation before the payout executes.
Return { isApproved: true } to proceed with the payout, or { isApproved: false } to cancel.
For the SDK-initiated mode, the SDK automatically sends the payout request to the PXP Gateway. For merchant-initiated mode, your backend triggers the payout via API.
The onPostPayout callback receives the transaction result. You can display a success message and redirect the customer to a confirmation page.
To use the withdrawal flow for payouts:
- Ensure your PayPal merchant account is onboarded with PXP.
- Have sufficient funds in your gateway balance to cover payout amounts and fees.
Set up your SDK configuration with wallet details to trigger the withdrawal flow.
import { PxpCheckout, IntentType, CurrencyType } from "@pxpio/web-components-sdk";
const pxpCheckoutSdk = PxpCheckout.initialize({
environment: "test", // or "production" for live
session: sessionData,
ownerId: "your-owner-id",
ownerType: "MerchantGroup",
transactionData: {
amount: 100,
currency: "USD" as CurrencyType,
entryType: "Ecom",
intent: {
paypal: IntentType.Payout
},
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString()
},
paypalConfig: {
payoutConfig: {
proceedPayoutWithSdk: true,
paypalWallet: {
email: "customer@example.com", // Stored PayPal email
payerId: "PAYER_ID_XXX" // Required: stored payer ID
}
}
}
});Providing paypalWallet or venmoWallet properties signals to the SDK that this is a returning customer with stored wallet details.
Add HTML containers for each component.
<div class="payout-page">
<h1>Withdraw funds</h1>
<div id="payout-amount-container"></div>
<div id="payout-receiver-container"></div>
<div id="payout-submit-container"></div>
</div>Use the amount, receiver, and submission components to build the withdrawal experience.
// Create the amount display component
const amountComponent = pxpCheckoutSdk.create('payout-amount', {
label: "Withdrawal Amount"
});
// Create the PayPal receiver display component
const receiverComponent = pxpCheckoutSdk.create('paypal-payout-receiver', {
label: "PayPal Account",
showMaskToggle: true, // Display a toggle (eye icon) to allow the customer to show or hide their full email
applyMask: true // Start with masked email
});
// Create the submission button for PayPal
const submitComponent = pxpCheckoutSdk.create('payout-submission', {
recipientWallet: "Paypal",
// OPTIONAL: Called before payout execution
onPrePayoutSubmit: async () => {
const confirmed = await showConfirmationDialog();
return {
isApproved: confirmed,
note: "Customer withdrawal" // Optional note (max 4000 chars)
};
},
// OPTIONAL: Called when the payout completes successfully
onPostPayout: (result) => {
console.log('Payout successful:', result.transactionId);
showSuccessMessage('Your withdrawal has been processed!');
window.location.href = `/success?txn=${result.transactionId}`;
},
// OPTIONAL: Called on any error
onError: (error) => {
console.error('Payout failed:', error);
showErrorMessage('Withdrawal failed. Please try again.');
}
});
// Mount all components
amountComponent.mount('payout-amount-container');
receiverComponent.mount('payout-receiver-container');
submitComponent.mount('payout-submit-container');When proceedPayoutWithSdk: true, the SDK handles the complete flow:
- The customer sees their stored wallet details.
- The customer clicks "Withdraw with PayPal/Venmo".
onPrePayoutSubmitis called for approval.- The SDK executes the payout automatically.
onPostPayoutis called with the result.
paypalConfig: {
payoutConfig: {
proceedPayoutWithSdk: true, // SDK handles payout execution
paypalWallet: {
email: "customer@example.com"
}
}
}Implement comprehensive error handling for the payout process.
const submitComponent = pxpCheckoutSdk.create('payout-submission', {
recipientWallet: "Paypal",
onError: (error) => {
console.error('Payout error:', error);
// Handle specific error types
switch (error.ErrorCode) {
case 'SDK0809':
showError('PayPal account information is missing.');
break;
case 'SDK0810':
case 'SDK0811':
case 'SDK0812':
showError('Invalid payout amount. Please contact support.');
break;
case 'SDK0813':
case 'SDK0814':
case 'SDK0815':
showError('Invalid currency.');
break;
case 'SDK1001':
showError('Recipient wallet is required.');
break;
case 'SDK1003':
showError('Unsupported wallet type.');
break;
case 'SDK1004':
showError('Payout note is too long (max 4000 characters).');
break;
default:
showError('An error occurred. Please try again or contact support.');
}
}
});Clean up the components when they're no longer needed.
amountComponent.unmount();
receiverComponent.unmount();
submitComponent.unmount();The following example shows a complete withdrawal flow implementation for returning customers.
import { PxpCheckout, IntentType, CurrencyType } from "@pxpio/web-components-sdk";
async function initializeWithdrawalPage() {
// Get session data from your backend
const sessionData = await getSessionDataFromBackend();
// Get stored customer wallet details
const customerWallet = await getStoredCustomerWallet();
const payoutAmount = 150.00;
// Initialize SDK with stored wallet details (withdrawal flow)
const pxpCheckoutSdk = PxpCheckout.initialize({
environment: "test",
session: sessionData,
ownerId: "Unity",
ownerType: "MerchantGroup",
transactionData: {
amount: payoutAmount,
currency: "USD" as CurrencyType,
entryType: "Ecom",
intent: { paypal: IntentType.Payout },
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString()
},
paypalConfig: {
payoutConfig: {
proceedPayoutWithSdk: true,
paypalWallet: {
email: customerWallet.email,
payerId: customerWallet.payerId
}
}
}
});
// Create amount display component
const amountComponent = pxpCheckoutSdk.create('payout-amount', {
label: "Withdrawal Amount",
style: {
fontSize: "24px",
fontWeight: "bold",
color: "#2e7d32"
}
});
// Create receiver display component
const receiverComponent = pxpCheckoutSdk.create('paypal-payout-receiver', {
label: "Sending to",
showMaskToggle: true,
applyMask: true,
style: {
backgroundColor: "#f5f5f5",
padding: "16px",
borderRadius: "8px"
}
});
// Create submission button
const submitComponent = pxpCheckoutSdk.create('payout-submission', {
recipientWallet: "Paypal",
submitText: "Withdraw to PayPal",
styles: {
base: {
backgroundColor: "#FFC438",
color: "#09090C",
borderRadius: "8px",
padding: "16px 32px",
fontSize: "18px",
fontWeight: "bold",
width: "100%"
},
hover: {
backgroundColor: "#F7B731"
}
},
onClick: () => {
trackEvent("payout_button_clicked");
},
onPrePayoutSubmit: async () => {
// Perform validation
const balance = await checkUserBalance();
if (balance < payoutAmount) {
showError("Insufficient balance for this withdrawal.");
return { isApproved: false };
}
// Show confirmation dialog
const confirmed = await showConfirmationModal({
title: "Confirm Withdrawal",
details: [
`Amount: $${payoutAmount.toFixed(2)}`,
`To: ${maskEmail(customerWallet.email)}`,
`Processing time: 1-2 business days`
],
confirmText: "Confirm Withdrawal",
cancelText: "Cancel"
});
if (!confirmed) {
trackEvent("payout_cancelled_by_user");
return { isApproved: false };
}
trackEvent("payout_approved");
return {
isApproved: true,
note: `Withdrawal request from user ${getCurrentUserId()}`
};
},
onPostPayout: (data) => {
console.log("Payout successful:", data);
trackEvent("payout_completed", {
merchantTransactionId: data.merchantTransactionId,
systemTransactionId: data.systemTransactionId,
amount: payoutAmount
});
// Update local balance
updateUserBalance(balance - payoutAmount);
// Show success animation
showSuccessAnimation();
// Redirect to success page
setTimeout(() => {
window.location.href = `/success?txn=${data.merchantTransactionId}`;
}, 1500);
},
onError: (error) => {
console.error("Payout error:", error);
trackEvent("payout_failed", {
errorCode: error.errorCode,
httpStatusCode: error.httpStatusCode
});
handlePayoutError(error);
}
});
// Mount all components
amountComponent.mount('payout-amount-container');
receiverComponent.mount('payout-receiver-container');
submitComponent.mount('payout-submit-container');
}
initializeWithdrawalPage();<div class="payout-page returning-customer">
<h1>Withdraw funds</h1>
<p class="subtitle">Review your withdrawal details below</p>
<div class="payout-card">
<div id="payout-amount-container"></div>
<div class="divider"></div>
<div id="payout-receiver-container"></div>
<div class="divider"></div>
<div id="payout-submit-container"></div>
</div>
<p class="info-text">
<a href="/account/change-payout-method">Use a different PayPal account?</a>
</p>
</div>This section describes the data received by the different callbacks as part of the withdrawal flow.
The onPrePayoutSubmit callback is called before payout execution. Return an object indicating whether to proceed.
onPrePayoutSubmit: async () => {
const approved = await showConfirmationDialog();
return {
isApproved: approved,
note: "Optional transaction note" // Max 4000 characters
};
}| Return Property | Description |
|---|---|
isApprovedboolean required | Whether to proceed with the payout. Return false to cancel. |
notestring | Optional note to include with the payout transaction (max 4000 characters). |
The onPostPayout callback receives the payout result when the transaction completes successfully.
onPostPayout: (data) => {
console.log("Transaction details:", data.merchantTransactionId, data.systemTransactionId);
}| Parameter | Description |
|---|---|
dataobject | Object containing transaction identifiers. |
data.merchantTransactionIdstring | Your unique identifier for the transaction. Use this with systemTransactionId to retrieve full authorisation details from Unity backend. |
data.systemTransactionIdstring | The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend. |
The onError callback receives error information when the payout fails.
{
errorCode: "NOT_AUTHENTICATED",
errorReason: "Invalid or missing credentials.",
httpStatusCode: 401,
correlationId: "69ba98c0-ecd8-4c19-8e17-bcbf61e66a24",
details: []
}| Parameter | Description |
|---|---|
errorobject | The error object containing details about what went wrong. |
error.correlationIdstring | Optional. The correlation ID for tracking the error. |
error.detailsarray | Optional. Array of additional error details. |
error.errorCodestring | The error code identifier (e.g., "NOT_AUTHENTICATED"). |
error.errorReasonstring | Human-readable error reason (e.g., "Invalid or missing credentials."). |
error.httpStatusCodenumber | The HTTP status code of the response. |