# Withdrawal flow

Send payouts to customers using PayPal account credentials provided by your backend.

## Overview

The withdrawal flow is designed for customers whose PayPal account credentials your backend provides. This provides a streamlined experience — the customer simply reviews the payout details and confirms.

This flow uses the receiver and submission components together, displaying the PayPal account information and providing a "Withdraw" button.

## Payout flow

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

### Step 1: Display payout details

The customer sees their PayPal email displayed alongside the payout amount. The receiver component shows the wallet destination, optionally masked for privacy.

### Step 2: Submission

The customer taps the "Withdraw with PayPal" button. The SDK validates the payout configuration and PayPal account details before proceeding. If validation fails, `onError` is triggered.

### Step 3: Payout approval (SDK-managed mode)

The `onPrePayoutSubmit` callback is triggered, giving you the opportunity to show a confirmation dialog or perform additional validation before the payout executes.

Return a `PrePayoutSubmitResult` with `isApproved: true` to proceed with the payout, or return `nil` to cancel.

### Step 4: Payout execution

For SDK-managed mode, the SDK automatically sends the payout request to the PXP gateway. For backend-managed 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 navigate the customer to a confirmation screen.

## Implementation

### Before you start

To use the withdrawal flow for payouts:

1. Ensure your PayPal merchant account is onboarded with PXP.
2. Have sufficient funds in your gateway balance to cover payout amounts and fees.
3. Have PayPal account credentials provided by your backend.


### Step 1: Configure your SDK

Set up your SDK configuration with PayPal account credentials from your backend to trigger the withdrawal flow.


```swift
import PXPCheckoutSDK

// Get session data from your backend
let sessionData = try await fetchSessionData()

// Get customer wallet credentials from your backend
let customerWallet = try await getCustomerWalletFromBackend(userId: currentUserId)

// Configure transaction data for payout
let transactionData = TransactionData(
    amount: Decimal(150.00),
    currency: "USD",
    entryType: .ecom,
    intent: TransactionIntentData(
        card: nil,
        paypal: .payout
    ),
    merchantTransactionId: "payout-\(UUID().uuidString)",
    merchantTransactionDate: { Date() }
)

// Configure PayPal with wallet credentials from backend (withdrawal flow)
let paypalConfig = PayPalConfig(
    payout: PayPalPayoutConfig(
        paypalWallet: PayPalWallet(
            email: customerWallet.email,       // PayPal email from backend
            payerId: customerWallet.payerId,   // Required: payer ID from backend
            proceedPayoutWithSdk: true
        )
    )
)

// Initialise the SDK
let checkoutConfig = CheckoutConfig(
    environment: .test,  // Use .live for production
    session: sessionData,
    transactionData: transactionData,
    merchantShopperId: "customer-123",
    ownerId: "your-owner-id",
    paypalConfig: paypalConfig
)

let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
```

The withdrawal flow requires PayPal account credentials provided by your backend in `PayPalWallet`:

- `payerId` is **required** for payout execution
- `email` is optional but recommended for display purposes in the receiver component



```swift
// Configure with credentials from your backend
PayPalWallet(
    email: "user@example.com",  // Optional, for display
    payerId: "PAYERID123",       // Required for payout
    proceedPayoutWithSdk: true
)
```

### Step 2: Create the components

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

The submission component automatically reads data from the amount and receiver components when the user taps "Withdraw":

- Amount value from `PayoutAmountComponent`
- Receiver email from `PayPalPayoutReceiverComponent`
- Payer ID from `PayPalWallet` configuration


You don't need to manually wire these components together - the SDK handles data collection automatically.


```swift
// Create the amount display component
let amountComponent = try pxpCheckout.create(
    .payoutAmount,
    componentConfig: PayoutAmountComponentConfig(
        label: "Withdrawal Amount"
    )
)

// Create the PayPal receiver display component
let receiverComponent = try pxpCheckout.create(
    .paypalPayoutReceiver,
    componentConfig: PayPalPayoutReceiverComponentConfig(
        label: "PayPal Account",
        showMaskToggle: true,  // Display a toggle (eye icon) to show/hide email
        applyMask: true        // Start with email masked
    )
)

// Create the payout submission component
let submissionComponent = try pxpCheckout.create(
    .payoutSubmission,
    componentConfig: PayoutSubmissionComponentConfig(
        submitText: "Withdraw with PayPal",
        
        // OPTIONAL: Called when button is clicked (before validation)
        onClick: {
            print("Withdrawal button clicked")
            // Track analytics, show loading state, etc.
        },
        
        // OPTIONAL: Called before payout execution (SDK-managed mode only)
        onPrePayoutSubmit: {
            let confirmed = await showConfirmationDialog()
            return confirmed ? PrePayoutSubmitResult(isApproved: true) : nil
        },
        
        // OPTIONAL: Called when payout completes (SDK-managed mode only)
        onPostPayout: { result in
            print("Payout successful:", result.merchantTransactionId)
            showSuccessMessage("Your withdrawal has been processed!")
            navigateToSuccessScreen(transactionId: result.merchantTransactionId)
        },
        
        // OPTIONAL: Called when user cancels
        onCancel: {
            print("Withdrawal cancelled by user")
            showMessage("No problem! Withdraw when you're ready.")
        },
        
        // OPTIONAL: Called on any error
        onError: { error in
            print("Error occurred:", error.errorMessage)
            showErrorMessage("Something went wrong. Please try again.")
        }
    )
)
```

### Step 3: Render the components

Render the components in your SwiftUI view.


```swift
struct WithdrawalFlowView: View {
    @State private var amountComponent: BaseComponent?
    @State private var receiverComponent: BaseComponent?
    @State private var submissionComponent: BaseComponent?
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Withdraw Funds")
                .font(.title)
            
            // Amount display
            if let amountComponent = amountComponent {
                amountComponent.buildContent()
                    .frame(height: 60)
            }
            
            // Receiver display
            if let receiverComponent = receiverComponent {
                receiverComponent.buildContent()
                    .frame(height: 60)
            }
            
            Spacer()
            
            // Submit button
            if let submissionComponent = submissionComponent {
                submissionComponent.buildContent()
                    .frame(height: 50)
            }
        }
        .padding()
        .onAppear {
            initialiseComponents()
        }
    }
    
    private func initialiseComponents() {
        Task {
            do {
                let components = try await createPayoutComponents()
                await MainActor.run {
                    self.amountComponent = components.amount
                    self.receiverComponent = components.receiver
                    self.submissionComponent = components.submission
                }
            } catch {
                print("Failed to initialise: \(error)")
            }
        }
    }
}
```

### Step 4: Handle payout modes

The `proceedPayoutWithSdk` parameter controls whether the SDK or your backend executes the payout. The default value is `false` (backend-managed).

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

1. The customer taps "Withdraw with PayPal".
2. `onPrePayoutSubmit` is called for approval.
3. The SDK executes the payout automatically.
4. `onPostPayout` is called with the result.



```swift
let paypalConfig = PayPalConfig(
    payout: PayPalPayoutConfig(
        paypalWallet: PayPalWallet(
            email: customerWallet.email,
            payerId: customerWallet.payerId,
            proceedPayoutWithSdk: true  // SDK handles payout execution
        )
    )
)
```

With `proceedPayoutWithSdk: true`, both `onPrePayoutSubmit` and `onPostPayout` callbacks are triggered. The SDK manages the payout execution after approval.

Backend-managed payout
When `proceedPayoutWithSdk: false` (default), your backend controls payout execution:

1. The customer taps "Withdraw with PayPal".
2. Components display the payout details.
3. Your backend must execute the payout via the PXP API.



```swift
let paypalConfig = PayPalConfig(
    payout: PayPalPayoutConfig(
        paypalWallet: PayPalWallet(
            email: customerWallet.email,
            payerId: customerWallet.payerId,
            proceedPayoutWithSdk: false  // Backend controls (default)
        )
    )
)
```

With `proceedPayoutWithSdk: false`, the `onPrePayoutSubmit` and `onPostPayout` callbacks aren't triggered. The SDK only displays payout information. Your backend must execute the payout via the PXP API independently.

This mode is ideal for:

- Custom validation or compliance checks
- Multi-step approval workflows
- Delayed or scheduled payouts
- Batch processing


### Step 5: Handle errors

Implement comprehensive error handling for the withdrawal process.


```swift
let submissionComponent = try pxpCheckout.create(
    .payoutSubmission,
    componentConfig: PayoutSubmissionComponentConfig(
        submitText: "Withdraw with PayPal",
        onPrePayoutSubmit: {
            return await showConfirmationDialog()
        },
        
        onError: { error in
            print("Payout error:", error)
            
            // Handle specific error types
            let userMessage: String
            
            switch error.errorCode {
            case "SDK0803":
                userMessage = "PayPal receiver information is missing."
            case "SDK0805":
                userMessage = "Invalid receiver type. Only email is supported."
            case "SDK0808":
                userMessage = "Invalid PayPal email format."
            case "SDK0809":
                userMessage = "PayPal account information is missing."
            case "SDK0810", "SDK0811", "SDK0812":
                userMessage = "Invalid payout amount. Please contact support."
            case "SDK0817":
                userMessage = "PayPal account identifier is too long."
            case "SDK0818":
                userMessage = "Invalid PayPal account."
            case "SDK0819":
                userMessage = "Payout transaction failed. Please try again."
            default:
                userMessage = "An error occurred. Please try again or contact support."
            }
            
            showErrorAlert(userMessage)
        }
    )
)
```

For a complete list of error codes including `SDK0812`-`SDK0816` (amount/currency validation), see [Data validation](/guides/checkout/components/ios/paypal/payouts/data-validation).

## Example

The following example shows a complete withdrawal flow implementation.


```swift
import SwiftUI
import PXPCheckoutSDK

struct PayoutWithdrawalFlowView: View {
    @State private var pxpCheckout: PxpCheckout?
    @State private var amountComponent: BaseComponent?
    @State private var receiverComponent: BaseComponent?
    @State private var submissionComponent: BaseComponent?
    @State private var isLoading = true
    @State private var errorMessage: String?
    @State private var showApprovalAlert = false
    @State private var pendingApprovalContinuation: CheckedContinuation<PrePayoutSubmitResult?, Never>?
    
    let currentUserId: String = "user123"
    var payoutAmount: Double = 0
    var customerEmail: String = ""
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Withdraw Funds")
                .font(.title)
                .fontWeight(.bold)
            
            if isLoading {
                ProgressView("Initialising...")
            } else if let error = errorMessage {
                Text(error)
                    .foregroundColor(.red)
                    .multilineTextAlignment(.center)
            } else {
                // Amount component
                if let amountComponent = amountComponent {
                    amountComponent.buildContent()
                        .frame(height: 60)
                }
                
                // Receiver component
                if let receiverComponent = receiverComponent {
                    receiverComponent.buildContent()
                        .frame(height: 60)
                }
                
                Spacer()
                
                // Submission button
                if let submissionComponent = submissionComponent {
                    submissionComponent.buildContent()
                        .frame(height: 50)
                }
            }
        }
        .padding()
        .alert("Confirm Payout", isPresented: $showApprovalAlert) {
            Button("Cancel", role: .cancel) {
                pendingApprovalContinuation?.resume(returning: nil)
                pendingApprovalContinuation = nil
            }
            Button("Confirm") {
                pendingApprovalContinuation?.resume(returning: 
                    PrePayoutSubmitResult(isApproved: true)
                )
                pendingApprovalContinuation = nil
            }
        } message: {
            Text("Send $\(String(format: "%.2f", payoutAmount)) to \(customerEmail)?")
        }
        .onAppear {
            initialisePayoutFlow()
        }
    }
    
    private func initialisePayoutFlow() {
        Task {
            do {
                isLoading = true
                
                // Get session data from backend
                let sessionResponse = try await getSessionDataFromBackend()
                
                payoutAmount = sessionResponse.payoutAmount
                customerEmail = sessionResponse.customerWallet.email
                
                // Configure transaction data
                let transactionData = TransactionData(
                    amount: Decimal(sessionResponse.payoutAmount),
                    currency: sessionResponse.currency,
                    entryType: .ecom,
                    intent: TransactionIntentData(
                        card: nil,
                        paypal: .payout
                    ),
                    merchantTransactionId: "payout-\(UUID().uuidString)",
                    merchantTransactionDate: { Date() }
                )
                
                // Configure PayPal with wallet credentials from backend
                let paypalConfig = PayPalConfig(
                    payout: PayPalPayoutConfig(
                        paypalWallet: PayPalWallet(
                            email: sessionResponse.customerWallet.email,
                            payerId: sessionResponse.customerWallet.payerId,
                            proceedPayoutWithSdk: true
                        )
                    )
                )
                
                // Initialise SDK
                let checkoutConfig = CheckoutConfig(
                    environment: .test,
                    session: sessionResponse.session,
                    transactionData: transactionData,
                    merchantShopperId: currentUserId,
                    ownerId: "Unity",
                    paypalConfig: paypalConfig
                )
                
                let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
                
                // Create components
                let amountComponent = try pxpCheckout.create(
                    .payoutAmount,
                    componentConfig: PayoutAmountComponentConfig(
                        label: "Withdrawal Amount"
                    )
                )
                
                let receiverComponent = try pxpCheckout.create(
                    .paypalPayoutReceiver,
                    componentConfig: PayPalPayoutReceiverComponentConfig(
                        label: "PayPal Account",
                        showMaskToggle: true,
                        applyMask: true
                    )
                )
                
                let submissionComponent = try pxpCheckout.create(
                    .payoutSubmission,
                    componentConfig: PayoutSubmissionComponentConfig(
                        submitText: "Withdraw with PayPal",
                        
                        onPrePayoutSubmit: { [self] in
                            return await showPayoutApproval()
                        },
                        
                        onPostPayout: { result in
                            handlePayoutSuccess(result)
                        },
                        
                        onCancel: {
                            handleCancellation()
                        },
                        
                        onError: { error in
                            handleError(error)
                        }
                    )
                )
                
                await MainActor.run {
                    self.pxpCheckout = pxpCheckout
                    self.amountComponent = amountComponent
                    self.receiverComponent = receiverComponent
                    self.submissionComponent = submissionComponent
                    self.isLoading = false
                }
                
            } catch {
                await MainActor.run {
                    self.errorMessage = "Failed to initialise: \(error.localizedDescription)"
                    self.isLoading = false
                }
            }
        }
    }
    
    private func showPayoutApproval() async -> PrePayoutSubmitResult? {
        return await withCheckedContinuation { continuation in
            Task { @MainActor in
                self.pendingApprovalContinuation = continuation
                self.showApprovalAlert = true
            }
        }
    }
    
    private func handlePayoutSuccess(_ result: MerchantSubmitResult) {
        print("🎉 Payout successful!")
        print("Merchant TX ID: \(result.merchantTransactionId)")
        print("System TX ID: \(result.systemTransactionId)")
        
        // Show success message and navigate
        // showSuccessMessage("Withdrawal completed successfully!")
        // navigateToSuccessScreen()
    }
    
    private func handleCancellation() {
        print("User cancelled payout")
        // showMessage("No problem! Withdraw when you're ready.")
    }
    
    private func handleError(_ error: BaseSdkException) {
        print("❌ Error: \(error.errorCode) - \(error.errorMessage)")
        errorMessage = "Payout failed: \(error.errorMessage)"
    }
    
    // Backend API call
    
    struct SessionResponse {
        let session: SessionData
        let payoutAmount: Double
        let currency: String
        let customerWallet: (email: String, payerId: String)
    }
    
    private func getSessionDataFromBackend() async throws -> SessionResponse {
        // Call your backend endpoint: POST /api/sessions/payout
        // Returns session data and customer wallet details
        fatalError("Implement your backend call")
    }
}
```

## Callback data

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

### onClick

The `onClick` callback is triggered when the user taps the withdrawal button, before validation begins.


```swift
onClick: {
    print("Withdrawal button clicked")
    // Track analytics, show loading state, etc.
}
```

This callback receives no parameters and is useful for analytics tracking and UI state management.

### onPrePayoutSubmit

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

This callback is only triggered when `proceedPayoutWithSdk: true`. If set to `false`, this callback won't be called and your backend must handle payout execution independently.


```swift
onPrePayoutSubmit: {
    let approved = await showConfirmationDialog()
    return approved ? PrePayoutSubmitResult(isApproved: true) : nil
}
```

| Return property | Description |
|  --- | --- |
| `isApproved`Bool | Whether to proceed with the payout. Return `nil` to cancel. |


### onPostPayout

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


```swift
onPostPayout: { result in
    print("Transaction ID:", result.systemTransactionId)
}
```

| Parameter | Description |
|  --- | --- |
| `result`MerchantSubmitResult | Object containing transaction identifiers. |
| `result.merchantTransactionId`String | Your unique identifier for the transaction. |
| `result.systemTransactionId`String | The system's unique identifier for the transaction. |


### onCancel

The `onCancel` callback is triggered when the user cancels the payout.


```swift
onCancel: {
    print("User cancelled payout")
    showMessage("You can try again anytime.")
}
```

### onError

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


```swift
onError: { error in
    print("Error:", error.errorCode, error.errorMessage)
}
```

| Parameter | Description |
|  --- | --- |
| `error`BaseSdkException | The error object containing details about what went wrong. |
| `error.errorCode`String | The error code identifier (e.g., "SDK0803"). |
| `error.errorMessage`String | Human-readable error message describing what went wrong. |
| `error.localizedDescription`String | The localised error description (same as `errorMessage`). |


## What's next?

Once you've implemented the withdrawal flow, explore these additional resources:

* [Payout submission component](/guides/checkout/components/ios/paypal/payouts/submission-component): Configure the submission button's appearance and behaviour.
* [Payout receiver component](/guides/checkout/components/ios/paypal/payouts/receiver-component): Customise the receiver display.
* [Payout amount component](/guides/checkout/components/ios/paypal/payouts/amount-component): Configure the amount display.
* [Events](/guides/checkout/components/ios/paypal/payouts/events): Handle additional payout events and callbacks.
* [Testing](/guides/checkout/components/ios/paypal/payouts/testing): Test your payout integration.