# Withdrawal flow

Send payouts to returning customers using stored wallet details.

## Overview

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.

## Before you start

To use the withdrawal flow for payouts:

1. **Install the Web SDK** - Install the latest version from npm:

```shell
npm i @pxpio/web-components-sdk
```
2. **Get your API credentials** - Obtain your token ID, token value, and client ID from the Unity Portal under **Merchant setup > Merchant groups > Inbound calls**. See the [card implementation guide](/guides/checkout/components/web/card/implementation#step-2-get-your-api-credentials) for detailed instructions.
3. **Create a session on your backend** - Use HMAC authentication to create a session with `intent.paypal: "Payout"`. See the [card implementation guide](/guides/checkout/components/web/card/implementation#step-3-create-a-session-on-your-backend) for detailed HMAC authentication instructions.
4. **Ensure your PayPal merchant account is onboarded** with PXP.
5. **Have sufficient funds** in your gateway balance to cover payout amounts and fees.


## Payout flow

The withdrawal flow consists of five key steps for returning customer payouts.

### Step 1: Display payout details

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.

### Step 2: Submission

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.

### Step 3: Payout approval

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.

### Step 4: Payout execution

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.

### Step 5: Payout result

The `onPostPayout` callback receives the transaction result. You can display a success message and redirect the customer to a confirmation page.

## Implementation

### Step 1: Configure your SDK

Set up your SDK configuration with wallet details to trigger the withdrawal flow.

PayPal
```typescript
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
      }
    }
  }
});
```

Venmo
```typescript
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,  // Venmo only supports USD
    entryType: "Ecom",
    intent: {
      paypal: IntentType.Payout
    },
    merchantTransactionId: crypto.randomUUID(),
    merchantTransactionDate: () => new Date().toISOString()
  },
  paypalConfig: {
    payoutConfig: {
      proceedPayoutWithSdk: true,
      venmoWallet: {
        receiver: "@venmo-user",      // or email or phone number
        recipientType: "UserHandle"   // "Email", "Phone", or "UserHandle"
      }
    }
  }
});
```

Providing `paypalWallet` or `venmoWallet` properties signals to the SDK that this is a returning customer with stored wallet details.

### Step 2: Add the HTML containers

Add HTML containers for each component.

```html
<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>
```

### Step 3: Create and mount the components

Use the amount, receiver, and submission components to build the withdrawal experience.

PayPal
```typescript
// 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');
```

Venmo
```typescript
// SDK configuration with Venmo wallet
const pxpCheckoutSdk = PxpCheckout.initialize({
  // ... other config
  paypalConfig: {
    payoutConfig: {
      proceedPayoutWithSdk: true,
      venmoWallet: {
        receiver: "@customer-handle",
        recipientType: "UserHandle"
      }
    }
  }
});

// Create Venmo receiver component
const amountComponent = pxpCheckoutSdk.create('payout-amount', {
  label: "Withdrawal Amount"
});

const venmoReceiverComponent = pxpCheckoutSdk.create('venmo-payout-receiver', {
  label: "Venmo Account",
  showMaskToggle: true
});

// Create submission with Venmo wallet type
const submitComponent = pxpCheckoutSdk.create('payout-submission', {
  recipientWallet: "Venmo",
  onPostPayout: (result) => {
    showSuccessMessage('Venmo payout successful!');
  }
});

amountComponent.mount('payout-amount-container');
venmoReceiverComponent.mount('payout-receiver-container');
submitComponent.mount('payout-submit-container');
```

PayPal and Venmo
```typescript
// SDK configuration with both PayPal and Venmo wallets available
const pxpCheckoutSdk = PxpCheckout.initialize({
  // ... other config
  paypalConfig: {
    payoutConfig: {
      proceedPayoutWithSdk: true,
      paypalWallet: {
        email: "customer@example.com",
        payerId: "PAYER_ID_XXX"
      },
      venmoWallet: {
        receiver: "@customer-handle",
        recipientType: "UserHandle"
      }
    }
  }
});

// Create shared amount component
const amountComponent = pxpCheckoutSdk.create('payout-amount', {
  label: "Withdrawal Amount"
});

// Create receiver components for each wallet type
const paypalReceiverComponent = pxpCheckoutSdk.create('paypal-payout-receiver', {
  label: "PayPal Account",
  showMaskToggle: true,
  applyMask: true
});

const venmoReceiverComponent = pxpCheckoutSdk.create('venmo-payout-receiver', {
  label: "Venmo Account",
  showMaskToggle: true
});

// Create submission component; choose wallet based on user selection
const submitComponent = pxpCheckoutSdk.create('payout-submission', {
  recipientWallet: "Paypal", // default
  
  onClick: () => {
    // Switch wallet based on UI selection before submitting
    const selectedWallet = getSelectedWalletType(); // "Paypal" or "Venmo"
    submitComponent.updateConfig({ recipientWallet: selectedWallet });
  },
  
  onPostPayout: (result) => {
    showSuccessMessage('Your withdrawal has been processed!');
  }
});

// Mount components (you can toggle which receiver is visible in your own UI)
amountComponent.mount('payout-amount-container');
paypalReceiverComponent.mount('payout-paypal-receiver-container');
venmoReceiverComponent.mount('payout-venmo-receiver-container');
submitComponent.mount('payout-submit-container');
```

### Step 4: Handle payout modes

SDK-initiated payout
When `proceedPayoutWithSdk: true`, the SDK handles the complete flow:

1. The customer sees their stored wallet details.
2. The customer clicks "Withdraw with PayPal/Venmo".
3. `onPrePayoutSubmit` is called for approval.
4. The SDK executes the payout automatically.
5. `onPostPayout` is called with the result.


```typescript
paypalConfig: {
  payoutConfig: {
    proceedPayoutWithSdk: true,  // SDK handles payout execution
    paypalWallet: {
      email: "customer@example.com"
    }
  }
}
```

Merchant-initiated
When `proceedPayoutWithSdk: false`, your backend controls payout execution:

1. The customer sees their stored wallet details.
2. The customer clicks "Withdraw with PayPal/Venmo".
3. The SDK validates the configuration.
4. Your backend calls the payout API when ready.


```typescript
paypalConfig: {
  payoutConfig: {
    proceedPayoutWithSdk: false,  // Merchant controls payout execution
    paypalWallet: {
      email: "customer@example.com"
    }
  }
}
```

### Step 5: Handle errors

Implement comprehensive error handling for the payout process.

```typescript
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.');
    }
  }
});
```

### Step 6: Unmount the components

Clean up the components when they're no longer needed.

```typescript
amountComponent.unmount();
receiverComponent.unmount();
submitComponent.unmount();
```

## Example

The following example shows a complete withdrawal flow implementation for returning customers.

```typescript
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;
  
  // Initialise 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();
```

```html
<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>
```

## Callback data

This section describes the data received by the different callbacks as part of the withdrawal flow.

### onPrePayoutSubmit

The `onPrePayoutSubmit` callback is called before payout execution. Return an object indicating whether to proceed.

```typescript
onPrePayoutSubmit: async () => {
  const approved = await showConfirmationDialog();
  return { 
    isApproved: approved,
    note: "Optional transaction note"  // Max 4000 characters
  };
}
```

| Return Property | Description |
|  --- | --- |
| `isApproved`boolean | Whether to proceed with the payout. Return `false` to cancel. |
| `note`string | Optional note to include with the payout transaction (max 4000 characters). |


### onPostPayout

The `onPostPayout` callback receives the payout result when the transaction completes successfully.

```typescript
onPostPayout: (data) => {
  console.log("Transaction details:", data.merchantTransactionId, data.systemTransactionId);
}
```

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing transaction identifiers. |
| `data.merchantTransactionId`string | Your unique identifier for the transaction. Use this with `systemTransactionId` to retrieve full authorisation details from the Unity backend. |
| `data.systemTransactionId`string | The system's unique identifier for the transaction. Use this with `merchantTransactionId` to retrieve full authorisation details from the Unity backend. |


### onError

The `onError` callback receives error information when the payout fails.

```typescript
{
  errorCode: "NOT_AUTHENTICATED",
  errorReason: "Invalid or missing credentials.",
  httpStatusCode: 401,
  correlationId: "69ba98c0-ecd8-4c19-8e17-bcbf61e66a24",
  details: []
}
```

| Parameter | Description |
|  --- | --- |
| `error`object | The error object containing details about what went wrong. |
| `error.correlationId`string | Optional. The correlation ID for tracking the error. |
| `error.details`array | Optional. Array of additional error details. |
| `error.errorCode`string | The error code identifier (e.g., "NOT_AUTHENTICATED"). |
| `error.errorReason`string | Human-readable error reason (e.g., "Invalid or missing credentials."). |
| `error.httpStatusCode`number | The HTTP status code of the response. |


## What's next?

Now that you've implemented the withdrawal flow, here are some recommended next steps:

- **[About configuration](/guides/checkout/components/web/paypal/payouts/about-configuration):** Learn how to customize the payout components.
- **[Testing](/guides/checkout/components/web/paypal/payouts/testing):** Use PayPal's sandbox environment to test payouts.
- **[Events](/guides/checkout/components/web/paypal/payouts/events):** Explore all available callbacks and event handling.
- **[Troubleshooting](/guides/checkout/components/web/paypal/payouts/troubleshooting):** Find solutions to common payout issues.