# Troubleshooting

Learn how to fix common issues with the PayPal payout components on iOS.

## Common issues and solutions

| Issue | Description | Next steps |
|  --- | --- | --- |
| **Payout fails with SDK0809** | `PayoutPayerIdRequiredException` - Payer ID is empty or missing. | Verify payer ID is passed in onPrePayoutSubmit result.Check account ID is not null or empty string.Implement proper state management for account ID. |
| **Payout fails with SDK0810** | `PayoutAmountInvalidException` - Amount is NaN or infinite. | Validate payout amount is a valid number.Check for division by zero in amount calculations.Ensure amount is properly formatted as Decimal.Verify currency conversion logic if applicable. |
| **Payout fails with SDK0811** | `PayoutAmountNotPositiveException` - Amount must be greater than zero. | Ensure payout amount is positive.Check for negative amounts from user input.Validate minimum payout amount ($1.00 USD).Implement input validation before payout. |
| **Payout fails with SDK0817** | `PayoutPayerIdMaxLengthException` - Payer ID exceeds 13 characters. | Validate payer ID length before payout.Check if account ID was incorrectly formatted.Ensure you're using the correct identifier from PayPal.Contact support if PayPal returns oversized IDs. |
| **Payout fails with SDK0818** | `PayoutPayerIdInvalidException` - Payer ID format is invalid. | Verify payer ID contains only alphanumeric characters.Check payer ID isn't corrupted or truncated.Ensure proper storage and retrieval of payer ID.Try re-authenticating with PayPal. |
| **Payout fails with SDK0819** | `PayoutFailedException` - Payout transaction failed. | Check PayPal Business account has sufficient funds.Verify recipient PayPal account exists and is active.Check payout amount is within limits.Review backend logs for detailed error from PayPal.Retry the payout after addressing the underlying issue. |
| **Button not rendering** | The PayPal payout button doesn't appear in the SwiftUI view. | Verify component is properly created and in view hierarchy.Check SwiftUI view provides adequate space (.frame(height:)).Ensure component reference is stored in @State.Check Console for component creation errors.Verify SDK initialisation completed successfully. |
| **onPrePayoutSubmit not called** | The merchant approval callback isn't triggered before payout submission. | This callback is ONLY called when ALL these conditions are met: proceedPayoutWithSdk: true in PayPalConfig, user clicked the payout submission button, and all validation passed (amount, email, payer ID).This callback isn't called when: proceedPayoutWithSdk: false (backend-managed mode), validation errors occurred before approval stage.Verify no errors occurred before this callback. |
| **onPostPayout not called** | The payout completion callback isn't triggered after approval. | Verify proceedPayoutWithSdk: true in PayPal config.Check onPrePayoutSubmit returned approval (not nil).Verify payout execution didn't fail (check onError).Check network connectivity during payout. |
| **Network connectivity issues** | SDK fails to communicate with PayPal or Unity APIs. | Check network reachability using NWPathMonitor.Verify internet connection is available.Handle network errors in onError callback.Implement retry logic for transient failures.Check for VPN or firewall blocking connections. |
| **SSL/TLS certificate errors** | Payout fails with SSL errors. | **Never ignore SSL errors in production.**Ensure device date/time is correct.Check certificate validity.Test on physical device (simulator may behave differently).Verify App Transport Security settings. |
| **Memory issues** | App crashes or becomes unresponsive during payout flow. | Monitor memory using Xcode Instruments.Check for memory leaks in closures.Ensure components are properly released.Avoid retain cycles in callback closures.Use [weak self] in async callbacks. |
| **SwiftUI state issues** | Component state is lost or doesn't update properly. | Use @State for component references.Store SDK instance in appropriate scope.Use @StateObject or @ObservedObject if needed.Ensure state updates happen on @MainActor.Check for proper state management in parent views. |
| **Event callbacks not firing** | `onError` or other callbacks aren't called. | Verify callbacks are defined in configuration.Check Console for error messages.Ensure callbacks are not nil.Add logging to all callbacks to verify execution.Check SDK initialisation completed successfully. |
| **Approval dialog issues** | The approval dialog in `onPrePayoutSubmit` doesn't work correctly. | Ensure proper async/await handling with continuations.Verify CheckedContinuation is properly resumed.Don't resume continuation multiple times.Handle edge cases (user dismisses dialog).Test cancellation flow thoroughly. |
| **Backend token exchange fails** | Authorization code exchange returns error. | Verify PayPal client credentials are correct.Check backend logs for specific error details.Verify Unity API credentials are valid. |
| **Backend "Token expired" error** | PayPal access token has expired when attempting payout. | Implement automatic token refresh before expiry.PayPal access tokens expire after 9 hours.Store token expiry time and refresh proactively.Handle refresh token failures gracefully. |
| **Backend "Payout failed - account not found"** | Payout execution fails because PayPal account can't be found. | Verify the payer_id is correct.Ensure the PayPal account exists and is active.Check payer ID wasn't corrupted during storage/retrieval. |
| **Backend "Insufficient permissions"** | Backend can't execute payouts due to permission issues. | Ensure PayPal app has Payouts API enabled in Developer Dashboard.Verify PayPal Business account is fully verified.Check PayPal account has sufficient balance for payouts.Contact PayPal support if permissions are missing. |
| **Simulator vs device differences** | Payout flow works in simulator but fails on device (or vice versa). | Always test on physical devices for production validation.Check for simulator-specific configurations.Verify code signing and entitlements.Check network conditions on device.Review device Console logs. |
| **App Store rejection** | App rejected due to PayPal payout integration issues. | Ensure all required privacy descriptions in Info.plist.Document payout functionality in review notes.Provide test PayPal sandbox accounts for App Review.Test in sandbox environment thoroughly.Ensure proper error handling for edge cases. |


## Understanding error sources

The SDK can report errors from three different sources. Understanding the distinction helps diagnose issues faster.

### SDK validation errors (SDK08xx, SDK09xx)

These occur before any API call, during client-side validation:

- Caught immediately in SDK
- Can be prevented with proper input validation
- Examples: `SDK0809`, `SDK0810`, `SDK0811`, `SDK0817`, `SDK0818`


### Unity API errors

These occur when communicating with the the Unity backend:

- Session errors (expired, invalid)
- Authentication errors
- Network connectivity issues
- Forwarded through `onError` callback with Unity error format


### PayPal API errors

These occur during actual payout execution:

- Wrapped in `SDK0819` (`PayoutFailedException`)
- Original PayPal error included in message
- Examples: insufficient funds, invalid account, blocked recipient


**Error handling strategy**:


```swift
import PXPCheckoutSDK

onError: { error in
    switch error.errorCode {
    case "SDK0809", "SDK0810", "SDK0811", "SDK0817", "SDK0818":
        // SDK validation error - fix input data
        print("Validation error: \(error.errorMessage)")
        
    case "SDK0819":
        // PayPal API error - check error message for details
        print("Payout failed: \(error.errorMessage)")
        // Message contains PayPal's error code and reason
        
    default:
        // Could be Unity API error or other SDK error
        print("Error: \(error.errorCode) - \(error.errorMessage)")
    }
}
```

## Additional error codes

The SDK includes additional validation error codes beyond those in the common issues table:

### Payout validation errors

| Error code | Exception | Description | Solution |
|  --- | --- | --- | --- |
| `SDK0803` | `PaypalPayoutReceiverRequiredException` | Receiver information is missing | Ensure `PayPalWallet.email` is configured in SDK initialisation |
| `SDK0804` | `PayoutReceiverTypeInvalidException` | Invalid receiver type | Use `.email` recipient type for PayPal payouts |
| `SDK0805` | `PaypalPayoutReceiverTypeEmailOnlyException` | Non-email receiver type used | PayPal payouts only support email recipient type |
| `SDK0806` | `PayoutWalletInvalidException` | Invalid wallet value | Use `.paypal` wallet type |
| `SDK0807` | `PayoutWalletRequiresEmailException` | Wallet type requires email | Ensure receiver type is `.email` when using PayPal wallet |
| SDK0808 | `PaypalPayoutEmailInvalidException` | Invalid email format | Validate email format before initialisation |
| `SDK0812` | `PayoutAmountRequiredException` | Amount is missing | Ensure `TransactionData.amount` is provided |
| `SDK0813` | `PayoutCurrencyRequiredException` | Currency code is missing | Provide valid currency code in `TransactionData` |
| `SDK0814` | `PayoutCurrencyLengthInvalidException` | Currency code not 3 characters | Use 3-letter ISO 4217 code (e.g., `USD`, `EUR`, `GBP`) |
| `SDK0815` | `PayoutCurrencyCodeInvalidException` | Invalid ISO 4217 currency code | Use valid ISO 4217 currency code |
| `SDK0816` | `PaypalPayoutEmailMaxLengthException` | Email exceeds 127 characters | Validate email length before initialisation |


## PayPal account setup issues

### "Payouts not enabled"

Your PayPal Business account doesn't have payout capability enabled.

**Solution**: Contact PayPal Support to enable payout capability for your business account. You may need to provide additional business verification documents.

## Production issues

### Payout failed in production but worked in sandbox

Payouts work in sandbox environment but fail when switching to production.

**Causes**:

- Live PayPal account not verified
- Insufficient funds in live account
- Payout limits exceeded
- Incorrect live credentials


**Solutions**:

- Verify your PayPal Business account is fully verified for live transactions.
- Ensure sufficient funds in your PayPal Business account.
- Contact PayPal to increase payout limits if needed.
- Double-check you're using live credentials (not sandbox).


### Webhooks not received in production

Webhook events are delivered in sandbox but not in production.

**Causes**:

- Webhook URL not publicly accessible
- SSL certificate issues
- Firewall blocking PayPal requests


**Solutions**:

- Verify webhook URL is publicly accessible (not localhost).
- Ensure HTTPS with a valid SSL certificate.
- Check firewall rules allow requests from PayPal IP ranges.
- Test with PayPal's webhook simulator in Developer Dashboard.


## Logging and debugging

To diagnose issues, enable comprehensive logging.

### Console logging

Use Console.app or Xcode's console to view logs:


```swift
import PXPCheckoutSDK

let config = PayoutSubmissionComponentConfig(
    buttonText: "Submit Payout",
    
    // Add verbose logging to callbacks
    onClick: {
        print("📱 PayPal payout button clicked")
    },
    
    onPrePayoutSubmit: {
        print("💰 Pre-payout approval requested")
        // ... approval logic ...
        return result
    },
    
    onPostPayout: { result in
        print("🎉 Payout successful!")
        print("   Merchant TX ID: \(result.merchantTransactionId)")
        print("   System TX ID: \(result.systemTransactionId)")
    },
    
    onCancel: {
        print("❌ User cancelled payout flow")
    },
    
    onError: { error in
        print("⚠️ Error occurred:")
        print("   Code: \(error.errorCode)")
        print("   Message: \(error.errorMessage)")
    }
)
```

### Console filters

These are suggested filters. The actual subsystem/category names depend on the SDK's logging implementation.

To find actual log identifiers:

1. Run your app with the SDK integrated.
2. Open Console.app.
3. Search for `PX` or `PayPal`.
4. Note the subsystem/category shown in log entries.
5. Use those exact identifiers in your filters.


Use Console.app filters to view specific logs:


```
# PXP Checkout logs
subsystem:com.pxp.checkout

# PayPal payout logs
process:YourAppName AND payout

# All SDK logs
category:PXPCheckoutSDK

# Example filter once you identify the subsystem:
subsystem:com.yourcompany.yourapp process:YourAppName
```

### Xcode debugging

Enable detailed logging in Xcode:

1. Open your scheme by going to **Product > Scheme > Edit Scheme**.
2. Select **Run > Arguments**.
3. Add these environment variables:
  - `OS_ACTIVITY_MODE` = `disable` (for cleaner logs)
  - `IDEPreferLogStreaming` = `YES`


## Useful code snippets

### Check network reachability

Use this network monitoring implementation before initiating payout flows or when diagnosing network-related errors. The payout SDK requires an active internet connection for payout execution.


```swift
import Network
import Combine

class NetworkMonitor: ObservableObject {
    static let shared = NetworkMonitor()
    private let monitor = NWPathMonitor()
    private let queue = DispatchQueue(label: "NetworkMonitor")
    
    @Published private(set) var isConnected: Bool = false
    @Published private(set) var connectionType: NWInterface.InterfaceType?
    
    private init() {}
    
    func startMonitoring() {
        monitor.pathUpdateHandler = { [weak self] path in
            DispatchQueue.main.async {
                self?.isConnected = path.status == .satisfied
                self?.connectionType = path.availableInterfaces.first?.type
                
                if path.status == .satisfied {
                    print("✅ Network connected via \(path.availableInterfaces.first?.type.debugDescription ?? "unknown")")
                } else {
                    print("⚠️ Network disconnected")
                }
            }
        }
        
        monitor.start(queue: queue)
    }
    
    func stopMonitoring() {
        monitor.cancel()
    }
    
    deinit {
        stopMonitoring()
    }
}

// Usage example:
NetworkMonitor.shared.startMonitoring()

if !NetworkMonitor.shared.isConnected {
    showAlert(title: "No Connection", 
             message: "Please check your internet connection and try again.")
    return
}

// Proceed with payout flow
```

### Validate payout configuration

Validate your payout configuration before creating components to catch errors early.


```swift
import PXPCheckoutSDK

struct PayoutValidationResult {
    let isValid: Bool
    let errors: [String]
}

func validatePayoutConfig(
    amount: Decimal,
    payerId: String?
) -> PayoutValidationResult {
    var errors: [String] = []
    
    // Validate amount
    if amount.isNaN {
        errors.append("Amount must be a valid number (SDK0810)")
    }
    
    if amount <= 0 {
        errors.append("Amount must be greater than zero (SDK0811)")
    }
    
    if amount < 1.00 {
        errors.append("Amount must be at least $1.00 (PayPal minimum)")
    }
    
    // Validate payer ID if provided
    if let payerId = payerId {
        if payerId.isEmpty {
            errors.append("Payer ID cannot be empty (SDK0809)")
        }
        
        if payerId.count > 13 {
            errors.append("Payer ID exceeds 13 characters (SDK0817)")
        }
        
        let alphanumericSet = CharacterSet.alphanumerics
        if payerId.unicodeScalars.contains(where: { !alphanumericSet.contains($0) }) {
            errors.append("Payer ID must be alphanumeric (SDK0818)")
        }
    }
    
    return PayoutValidationResult(
        isValid: errors.isEmpty,
        errors: errors
    )
}

// Usage example:
let validation = validatePayoutConfig(
    amount: Decimal(100.00),
    payerId: storedPayerId
)

if !validation.isValid {
    print("❌ Validation errors: \(validation.errors.joined(separator: ", "))")
    showErrorDialog(validation.errors.first ?? "Validation failed")
    return
}

// Proceed with payout
```

### Handle approval dialog with continuation

Properly implement async approval dialogs using Swift continuations:


```swift
import SwiftUI
import PXPCheckoutSDK

struct PayoutView: View {
    @State private var showApprovalAlert = false
    @State private var pendingApprovalContinuation: CheckedContinuation<PrePayoutSubmitResult?, Never>?
    @State private var storedPayerId: String?
    
    let payoutAmount = 100.00
    
    var body: some View {
        VStack {
            // ... payout UI ...
        }
        .alert("Confirm Payout", isPresented: $showApprovalAlert) {
            Button("Cancel", role: .cancel) {
                // User rejected - resume with nil
                pendingApprovalContinuation?.resume(returning: nil)
                pendingApprovalContinuation = nil
            }
            Button("Confirm") {
                // User approved - resume with approval result
                pendingApprovalContinuation?.resume(returning: 
                    PrePayoutSubmitResult(
                        isApproved: true,
                        payerId: storedPayerId ?? ""
                    )
                )
                pendingApprovalContinuation = nil
            }
        } message: {
            Text("Send $\(String(format: "%.2f", payoutAmount)) to your PayPal account?")
        }
    }
    
    private func createPayoutComponent() {
        let config = PayoutSubmissionComponentConfig(
            buttonText: "Submit Payout",
            
            onPrePayoutSubmit: { [self] in
                // Use withCheckedContinuation for async dialog
                return await withCheckedContinuation { continuation in
                    Task { @MainActor in
                        self.pendingApprovalContinuation = continuation
                        self.showApprovalAlert = true
                    }
                }
            }
        )
        
        // ... create component ...
    }
}
```

### Debug SwiftUI view hierarchy

Use these debugging extensions when components don't appear or layout issues occur:


```swift
extension View {
    func debugPrint(_ message: String) -> some View {
        print("🔍 \(message)")
        return self
    }
    
    func debugBorder(_ color: Color = .red) -> some View {
        self.border(color, width: 2)
    }
}

// Usage example:
struct PayoutView: View {
    var body: some View {
        VStack {
            if let component = payoutComponent {
                component.buildContent()
                    .frame(height: 50)
                    .debugBorder(.red) // Visualise button area
                    .debugPrint("Payout button rendered")
            } else {
                Text("Loading...")
                    .debugPrint("Still loading component")
            }
        }
        .debugBorder(.blue) // Visualise container
    }
}
```

### Handle async initialisation errors

Implement proper error handling when SDK or component initialisation might fail:


```swift
import SwiftUI
import PXPCheckoutSDK

struct PayoutView: View {
    @State private var payoutComponent: BaseComponent?
    @State private var isLoading = true
    @State private var errorMessage: String?
    @State private var initTask: Task<Void, Never>?
    
    var body: some View {
        VStack {
            if isLoading {
                ProgressView("Initialising...")
            } else if let error = errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
                Button("Retry") {
                    initTask = Task {
                        await createPayoutComponent()
                    }
                }
            } else if let component = payoutComponent {
                component.buildContent()
            }
        }
        .task {
            await createPayoutComponent()
        }
        .onDisappear {
            initTask?.cancel()
        }
    }
    
    @MainActor
    private func createPayoutComponent() async {
        isLoading = true
        errorMessage = nil
        
        do {
            // Initialise SDK
            let checkoutConfig = CheckoutConfig(
                environment: .test,
                session: sessionData,
                transactionData: transactionData,
                merchantShopperId: "user-123",
                ownerId: "merchant-456",
                paypalConfig: paypalConfig
            )
            
            let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
            
            // Create payout component
            let config = PayoutSubmissionComponentConfig(
                buttonText: "Submit Payout"
            )
            
            let component = try pxpCheckout.create(
                .payoutSubmission,
                componentConfig: config
            )
            
            self.payoutComponent = component
            
        } catch let error as NSError {
            print("❌ Initialisation error: \(error.localizedDescription)")
            print("Error domain: \(error.domain)")
            print("Error code: \(error.code)")
            print("Error userInfo: \(error.userInfo)")
            
            self.errorMessage = "Failed to initialise payout: \(error.localizedDescription)"
            
            // Track error for analytics
            logError(error)
        }
        
        isLoading = false
    }
    
    func logError(_ error: Error) {
        // Log to your analytics/crash reporting service
        print("📊 Logging error to analytics")
    }
}
```

### Track payout funnel metrics

Monitor your payout funnel to identify drop-off points:

This example uses a placeholder `Analytics` class. Replace with your actual analytics service (Firebase Analytics, Mixpanel, Amplitude, or your custom service).


```swift
import PXPCheckoutSDK

// Example with common analytics services:

// Firebase Analytics:
// Analytics.logEvent("payout_started", parameters: [...])

// Mixpanel:
// Mixpanel.mainInstance().track(event: "payout_started", properties: [...])

// Amplitude:
// Amplitude.instance().logEvent("payout_started", withEventProperties: [...])

// Custom analytics service:
// YourAnalyticsService.shared.trackEvent(name: "payout_started", properties: [...])

let config = PayoutSubmissionComponentConfig(
    buttonText: "Submit Payout",
    
    onClick: {
        Analytics.track("payout_started", properties: [
            "timestamp": Date().timeIntervalSince1970,
            "amount": payoutAmount
        ])
    },
    
    onPrePayoutSubmit: {
        Analytics.track("payout_approval_requested", properties: [
            "timestamp": Date().timeIntervalSince1970
        ])
        
        let result = await showApprovalDialog()
        
        if result == nil {
            Analytics.track("payout_approval_rejected", properties: [
                "timestamp": Date().timeIntervalSince1970
            ])
        } else {
            Analytics.track("payout_approval_granted", properties: [
                "timestamp": Date().timeIntervalSince1970
            ])
        }
        
        return result
    },
    
    onPostPayout: { result in
        Analytics.track("payout_completed", properties: [
            "transactionId": result.merchantTransactionId,
            "timestamp": Date().timeIntervalSince1970,
            "amount": payoutAmount
        ])
    },
    
    onCancel: {
        Analytics.track("payout_cancelled", properties: [
            "timestamp": Date().timeIntervalSince1970
        ])
    },
    
    onError: { error in
        Analytics.track("payout_error", properties: [
            "errorCode": error.errorCode,
            "errorMessage": error.errorMessage,
            "timestamp": Date().timeIntervalSince1970
        ])
    }
)
```

## Testing your integration

For comprehensive testing guidance including sandbox setup, test accounts, test scenarios, and moving to production, see the [Testing](/guides/checkout/components/ios/paypal/payouts/testing) guide.