Skip to content

Data validation

Learn about built-in validation and implement additional scenarios for payouts.

Overview

The PayPal payout components include comprehensive validation to ensure data integrity and compliance with PayPal's requirements and payout regulations. All built-in validation is performed before executing payouts. If validation fails, the SDK will trigger the onError callback with detailed error information.

You can also implement custom validation for business rules, compliance checks, and fraud prevention.

Applicable components

This validation guide applies to the payout submission component (PayoutSubmissionComponentConfig) for withdrawal flows.

Built-in validation

By default, the PayPal payout submission component validates that:

  • The payer ID is provided, properly formatted, and within length constraints (maximum 13 characters).
  • The payout amount is a valid, positive number (not NaN or infinite).
  • The transaction data contains all required fields with proper formats.
  • Session credentials are valid and not expired.

Error codes

The PayPal payout submission component returns structured error codes accessible via error.errorCode.

Error code Description
SDK0809 / PayoutPayerIdRequiredExceptionThe payer ID is empty or not provided. The payer ID must be provided in PayPalWallet configuration.
SDK0810 / PayoutAmountInvalidExceptionThe payout amount is NaN (Not a Number) or infinite. Check the transaction amount configuration.
SDK0811 / PayoutAmountNotPositiveExceptionThe payout amount must be greater than zero. Negative or zero amounts aren't allowed.
SDK0812 / PayoutAmountRequiredExceptionThe payout amount is required but wasn't provided in the transaction data.
SDK0813 / PayoutCurrencyRequiredExceptionThe currency code is required. Must be a non-empty string.
SDK0814 / PayoutCurrencyLengthInvalidExceptionThe currency code must be exactly 3 characters (ISO 4217 format, e.g., "USD", "EUR").
SDK0815 / PayoutCurrencyCodeInvalidExceptionInvalid currency code. Must be a valid ISO 4217 currency code.
SDK0816 / PayoutEmailMaxLengthExceptionThe email address exceeds maximum length of 127 characters.
SDK0817 / PayoutPayerIdMaxLengthExceptionThe payer ID exceeds the maximum length of 13 characters.
SDK0818 / PayoutPayerIdInvalidExceptionThe payer ID contains invalid characters. Only alphanumeric characters (A-Z, a-z, 0-9) are allowed.
SDK0819 / PayoutFailedExceptionThe payout transaction failed. Check the error message for details.
SDK0803 / PaypalPayoutReceiverRequiredExceptionThe receiver is required. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).
SDK0804 / PayoutReceiverTypeInvalidExceptionInvalid receiver type. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).
SDK0805 / PaypalPayoutReceiverTypeEmailOnlyExceptionThe receiver type must be Email. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).
SDK0806 / PayoutWalletInvalidExceptionInvalid wallet value. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).
SDK0807 / PayoutWalletRequiresEmailExceptionThe wallet requires an email receiver type. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).
SDK0808 / PaypalPayoutEmailInvalidExceptionInvalid email format. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent).

Always implement error handling in the onError callback to catch and handle these validation errors appropriately.

Validation example

The following example shows error handling for the payout submission component.

let config = PayoutSubmissionComponentConfig(
    submitText: "Withdraw with PayPal",
    
    onError: { error in
        print("Validation error: \(error.errorMessage)")
        print("Error code: \(error.errorCode)")
        
        // Handle specific validation errors
        switch error.errorCode {
        case "SDK0803":
            showError("PayPal receiver information is missing.")
            logValidationError("missing_receiver")
            
        case "SDK0805":
            showError("Invalid receiver type. Only email is supported.")
            logValidationError("invalid_receiver_type")
            
        case "SDK0808":
            showError("Invalid PayPal email format.")
            logValidationError("invalid_email")
            
        case "SDK0809":
            showError("PayPal payer ID is missing. Please link your account.")
            logValidationError("missing_payer_id")
            
        case "SDK0810":
            showError("Invalid payout amount. Please contact support.")
            logValidationError("invalid_amount_format")
            
        case "SDK0811":
            showError("Payout amount must be greater than zero.")
            logValidationError("negative_or_zero_amount")
            
        case "SDK0812":
            showError("Payout amount is required.")
            logValidationError("missing_amount")
            
        case "SDK0813", "SDK0814", "SDK0815":
            showError("Invalid currency configuration. Please contact support.")
            logValidationError("currency_error")
            
        case "SDK0817":
            showError("PayPal account identifier is too long.")
            logValidationError("payer_id_too_long")
            
        case "SDK0818":
            showError("Invalid PayPal account format.")
            logValidationError("invalid_payer_id_format")
            
        case "SDK0819":
            showError("Payout transaction failed. Please try again.")
            logValidationError("payout_failed")
            
        default:
            showError("Payout failed. Please try again or contact support.")
            logValidationError("unknown_error", details: error.errorCode)
        }
        
        // Log for debugging
        Crashlytics.crashlytics().log("Payout error [\(error.errorCode)]: \(error.errorMessage)")
    }
)

Custom validation

Custom validation allows you to implement business-specific rules, compliance checks, and fraud prevention before executing payouts.

When to use custom validation

Before component creation:

  • validatePayoutAmount(): Check amount limits before showing the UI.
  • validatePayoutFrequency(): Check daily/weekly/monthly limits before allowing payout request
  • validatePayoutCompliance(): Pre-check KYC status, AML requirements.

In onPrePayoutSubmit callback (when proceedPayoutWithSdk: true):

  • validatePayoutEligibility(): Final check of user KYC status before approval.
  • performComplianceChecks(): Perform AML, sanctions checks at approval time.
  • validateFraudRisk(): Calculate risk score before executing payout.

onPrePayoutSubmit return types

The PayoutSubmissionComponentConfig uses PrePayoutSubmitResult:

onPrePayoutSubmit: (() async -> PrePayoutSubmitResult?)

// Return object only includes isApproved
return PrePayoutSubmitResult(
    isApproved: true  // No payerId - comes from PayPalWallet config
)

The payer ID is already provided in the PayPalWallet configuration during SDK initialisation.

Pre-payout approval validation

Run comprehensive validation before approving payouts to catch issues early and prevent failed transactions.

let config = PayoutSubmissionComponentConfig(
    submitText: "Withdraw with PayPal",
    
    onPrePayoutSubmit: {
        do {
            // 1. Validate payout eligibility
            let eligibilityCheck = try await validatePayoutEligibility(
                userId: currentUser.id,
                accountStatus: currentUser.accountStatus,
                kycStatus: currentUser.kycStatus
            )
            
            guard eligibilityCheck.isEligible else {
                await MainActor.run {
                    showError("You are not eligible for payouts: \(eligibilityCheck.reason)")
                }
                return nil
            }
            
            // 2. Validate payout amount
            let amountValidation = try await validatePayoutAmount(
                amount: getCurrentPayoutAmount(),
                currency: "USD",
                userId: currentUser.id
            )
            
            guard amountValidation.isValid else {
                await MainActor.run {
                    showError(amountValidation.errorMessage ?? "Invalid payout amount")
                }
                return nil
            }
            
            // 3. Compliance checks
            let complianceCheck = try await performComplianceChecks(
                userId: currentUser.id,
                amount: getCurrentPayoutAmount(),
                country: currentUser.country,
                accountAge: currentUser.accountAge
            )
            
            guard complianceCheck.passed else {
                await MainActor.run {
                    showError("Compliance check failed: \(complianceCheck.reason)")
                }
                return nil
            }
            
            // 4. Anti-fraud validation
            let fraudCheck = try await validateFraudRisk(
                userId: currentUser.id,
                amount: getCurrentPayoutAmount(),
                payoutFrequency: getUserPayoutHistory()
            )
            
            if fraudCheck.riskLevel == .high {
                // Require additional verification
                let verified = try await requestAdditionalVerification()
                guard verified else {
                    await MainActor.run {
                        showError("Additional verification required for this payout")
                    }
                    return nil
                }
            }
            
            // 5. Balance verification
            let balanceCheck = try await validateSufficientBalance(
                merchantId: getMerchantId(),
                payoutAmount: getCurrentPayoutAmount()
            )
            
            guard balanceCheck.hasSufficientFunds else {
                await MainActor.run {
                    showError("Insufficient funds to process this payout")
                }
                return nil
            }
            
            // 6. Show approval dialog
            let approved = await showApprovalDialog(
                amount: getCurrentPayoutAmount(),
                currency: "USD",
                recipient: getUserPayPalEmail()
            )
            
            guard approved else {
                return nil
            }
            
            // All validations passed - approve payout
            return PrePayoutSubmitResult(isApproved: true)
            
        } catch {
            print("Pre-payout validation failed: \(error.localizedDescription)")
            await MainActor.run {
                showError("Validation failed: \(error.localizedDescription)")
            }
            return nil
        }
    }
)

Validate payout amount limits

Ensure the payout amount meets minimum and maximum requirements.

struct PayoutAmountValidation {
    let isValid: Bool
    let errorMessage: String?
}

func validatePayoutAmount(
    amount: Double,
    currency: String,
    userId: String
) async throws -> PayoutAmountValidation {
    // Define limits per currency
    let currencyLimits: [String: (min: Double, max: Double)] = [
        "USD": (1.0, 60000.0),
        "EUR": (1.0, 50000.0),
        "GBP": (1.0, 45000.0)
    ]
    
    guard let (minAmount, maxAmount) = currencyLimits[currency] else {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Unsupported currency: \(currency)"
        )
    }
    
    // Currency symbols for display
    let currencySymbols: [String: String] = [
        "USD": "$",
        "EUR": "€",
        "GBP": "£"
    ]
    let symbol = currencySymbols[currency] ?? currency
    
    // Validate amount is positive and valid
    guard amount.isFinite && !amount.isNaN else {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Invalid payout amount format"
        )
    }
    
    if amount <= 0 {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Payout amount must be greater than zero"
        )
    }
    
    if amount < minAmount {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Minimum payout amount is \(symbol)\(minAmount)"
        )
    }
    
    if amount > maxAmount {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Maximum payout amount is \(symbol)\(maxAmount)"
        )
    }
    
    // Check user-specific limits
    let userLimit = try await getUserPayoutLimit(userId: userId)
    if amount > userLimit {
        return PayoutAmountValidation(
            isValid: false,
            errorMessage: "Amount exceeds your payout limit of \(symbol)\(userLimit)"
        )
    }
    
    return PayoutAmountValidation(isValid: true, errorMessage: nil)
}

// Usage before creating component
let validation = try await validatePayoutAmount(
    amount: 100.0,
    currency: "USD",
    userId: currentUser.id
)

if !validation.isValid {
    showError(validation.errorMessage ?? "Invalid payout amount")
    return
}

// Proceed with payout component creation

Validate payer ID format

Validate payer ID format before storing during account linking or before using in payouts.

struct PayerIdValidation {
    let isValid: Bool
    let errorMessage: String?
}

func validatePayerId(_ payerId: String?) -> PayerIdValidation {
    // Check if payer ID is provided
    guard let payerId = payerId, !payerId.isEmpty else {
        return PayerIdValidation(
            isValid: false,
            errorMessage: "Payer ID is required"
        )
    }
    
    // Check length (max 13 characters)
    if payerId.count > 13 {
        return PayerIdValidation(
            isValid: false,
            errorMessage: "Payer ID must be 13 characters or less"
        )
    }
    
    // Check format (alphanumeric)
    let alphanumericSet = CharacterSet.alphanumerics
    if payerId.rangeOfCharacter(from: alphanumericSet.inverted) != nil {
        return PayerIdValidation(
            isValid: false,
            errorMessage: "Payer ID must contain only letters and numbers"
        )
    }
    
    // Optional: Additional custom format validation
    // Note: This is an example of custom business logic - PayPal payer IDs
    // don't have a guaranteed prefix pattern. Remove or modify based on your needs.
    // if !payerId.hasPrefix("P") && !payerId.hasPrefix("M") {
    //     return PayerIdValidation(
    //         isValid: false,
    //         errorMessage: "Invalid payer ID format"
    //     )
    // }
    
    return PayerIdValidation(isValid: true, errorMessage: nil)
}

Calculate fraud risk score

Calculate a comprehensive fraud risk score for payout transactions. Use this validation in onPrePayoutSubmit when proceedPayoutWithSdk: true.

struct PayoutRiskFactors {
    let velocityScore: Double
    let accountAgeScore: Double
    let activityScore: Double
    let complianceScore: Double
}

struct PayoutFraudRiskResult {
    let score: Double
    let riskLevel: RiskLevel
    let factors: PayoutRiskFactors
    let requiresVerification: Bool
}

enum RiskLevel {
    case low, medium, high
}

func calculatePayoutFraudRisk(
    userId: String,
    amount: Double,
    payoutHistory: [PayoutTransaction]
) async throws -> PayoutFraudRiskResult {
    // Calculate individual risk factors
    let velocityScore = try await checkPayoutVelocity(
        userId: userId,
        recentPayouts: payoutHistory,
        requestedAmount: amount
    )
    
    let accountAgeScore = try await evaluateAccountAge(userId: userId)
    
    let activityScore = try await analyzeUserActivity(userId: userId)
    
    let complianceScore = try await checkComplianceHistory(userId: userId)
    
    let factors = PayoutRiskFactors(
        velocityScore: velocityScore,
        accountAgeScore: accountAgeScore,
        activityScore: activityScore,
        complianceScore: complianceScore
    )
    
    // Calculate overall risk score (weighted average)
    let totalScore = (velocityScore * 0.4 +
                     accountAgeScore * 0.2 +
                     activityScore * 0.2 +
                     complianceScore * 0.2)
    
    // Determine risk level
    let riskLevel: RiskLevel
    let requiresVerification: Bool
    
    if totalScore > 0.75 {
        riskLevel = .high
        requiresVerification = true
    } else if totalScore > 0.45 {
        riskLevel = .medium
        requiresVerification = amount > 1000.0 // Verify if amount > $1000
    } else {
        riskLevel = .low
        requiresVerification = false
    }
    
    return PayoutFraudRiskResult(
        score: totalScore,
        riskLevel: riskLevel,
        factors: factors,
        requiresVerification: requiresVerification
    )
}

// Usage in payout component
let config = PayoutSubmissionComponentConfig(
    submitText: "Withdraw with PayPal",
    
    onPrePayoutSubmit: {
        let riskResult = try await calculatePayoutFraudRisk(
            userId: currentUser.id,
            amount: getCurrentPayoutAmount(),
            payoutHistory: getUserPayoutHistory()
        )
        
        // Log risk assessment
        logRiskAssessment(
            userId: currentUser.id,
            riskScore: riskResult.score,
            riskLevel: riskResult.riskLevel
        )
        
        switch riskResult.riskLevel {
        case .high:
            print("High risk payout detected")
            
            // Block payout or require manual review
            await MainActor.run {
                showError("This payout requires additional review. Please contact support.")
            }
            
            // Flag for manual review
            await flagPayoutForManualReview(
                userId: currentUser.id,
                amount: getCurrentPayoutAmount(),
                riskScore: riskResult.score
            )
            
            return nil
            
        case .medium:
            print("Medium risk payout")
            
            if riskResult.requiresVerification {
                // Require additional verification
                let verified = try await requestAdditionalVerification()
                guard verified else {
                    await MainActor.run {
                        showError("Additional verification required")
                    }
                    return nil
                }
            }
            
            // Add extra monitoring
            await flagPayoutForMonitoring(
                userId: currentUser.id,
                riskScore: riskResult.score
            )
            
        case .low:
            print("Low risk payout - proceeding normally")
        }
        
        // Show approval dialog
        let approved = await showApprovalDialog()
        guard approved else { return nil }
        
        return PrePayoutSubmitResult(isApproved: true)
    }
)

Validate payout frequency

Ensure users don't exceed daily, weekly, or monthly payout limits. Use this validation before component creation to provide immediate feedback.

struct PayoutFrequencyValidation {
    let isValid: Bool
    let errorMessage: String?
    let nextAvailableDate: Date?
}

func validatePayoutFrequency(
    userId: String,
    requestedAmount: Double
) async throws -> PayoutFrequencyValidation {
    let history = try await getUserPayoutHistory(userId: userId)
    let now = Date()
    
    // Daily limit: Max 3 payouts per day
    let todayPayouts = history.filter { payout in
        Calendar.current.isDateInToday(payout.createdAt)
    }
    
    if todayPayouts.count >= 3 {
        let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: now)!
        let startOfTomorrow = Calendar.current.startOfDay(for: tomorrow)
        
        return PayoutFrequencyValidation(
            isValid: false,
            errorMessage: "Daily payout limit reached (3 payouts per day)",
            nextAvailableDate: startOfTomorrow
        )
    }
    
    // Weekly limit: Max $10,000 per week
    let weekAgo = Calendar.current.date(byAdding: .day, value: -7, to: now)!
    let weeklyPayouts = history.filter { $0.createdAt > weekAgo }
    let weeklyTotal = weeklyPayouts.reduce(0.0) { $0 + $1.amount }
    
    if weeklyTotal + requestedAmount > 10000.0 {
        return PayoutFrequencyValidation(
            isValid: false,
            errorMessage: "Weekly payout limit of $10,000 would be exceeded. Current total: $\(String(format: "%.2f", weeklyTotal))",
            nextAvailableDate: Calendar.current.date(byAdding: .day, value: 7, to: weekAgo)
        )
    }
    
    // Monthly limit: Max $50,000 per month
    let monthAgo = Calendar.current.date(byAdding: .month, value: -1, to: now)!
    let monthlyPayouts = history.filter { $0.createdAt > monthAgo }
    let monthlyTotal = monthlyPayouts.reduce(0.0) { $0 + $1.amount }
    
    if monthlyTotal + requestedAmount > 50000.0 {
        return PayoutFrequencyValidation(
            isValid: false,
            errorMessage: "Monthly payout limit of $50,000 would be exceeded. Current total: $\(String(format: "%.2f", monthlyTotal))",
            nextAvailableDate: Calendar.current.date(byAdding: .month, value: 1, to: monthAgo)
        )
    }
    
    return PayoutFrequencyValidation(
        isValid: true,
        errorMessage: nil,
        nextAvailableDate: nil
    )
}

// Usage before creating components
let frequencyCheck = try await validatePayoutFrequency(
    userId: currentUser.id,
    requestedAmount: 100.0
)

if !frequencyCheck.isValid {
    var errorMessage = frequencyCheck.errorMessage ?? "Payout limit exceeded"
    
    if let nextDate = frequencyCheck.nextAvailableDate {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        errorMessage += "\nNext available: \(formatter.string(from: nextDate))"
    }
    
    showError(errorMessage)
    return
}

// Proceed with component creation

Validate compliance requirements

Check KYC, AML, and other regulatory compliance before processing payouts. Use this validation in onPrePayoutSubmit (when proceedPayoutWithSdk: true) or before component creation.

struct ComplianceValidation {
    let passed: Bool
    let reason: String?
    let requiresAction: Bool
    let actionUrl: String?
}

func validatePayoutCompliance(
    userId: String,
    amount: Double,
    country: String
) async throws -> ComplianceValidation {
    // 1. Check KYC (Know Your Customer) status
    let kycStatus = try await getKYCStatus(userId: userId)
    
    switch kycStatus {
    case .notStarted:
        return ComplianceValidation(
            passed: false,
            reason: "Identity verification required before receiving payouts",
            requiresAction: true,
            actionUrl: "/kyc/start"
        )
        
    case .pending:
        return ComplianceValidation(
            passed: false,
            reason: "Identity verification is pending review",
            requiresAction: false,
            actionUrl: nil
        )
        
    case .rejected:
        return ComplianceValidation(
            passed: false,
            reason: "Identity verification was rejected. Please contact support.",
            requiresAction: true,
            actionUrl: "/support"
        )
        
    case .verified:
        // KYC passed, continue checks
        break
    }
    
    // 2. Check AML (Anti-Money Laundering) thresholds
    if amount > 3000.0 {
        let amlCheck = try await performAMLCheck(
            userId: userId,
            amount: amount,
            country: country
        )
        
        guard amlCheck.passed else {
            return ComplianceValidation(
                passed: false,
                reason: "Additional documentation required for payouts over $3,000",
                requiresAction: true,
                actionUrl: "/compliance/aml"
            )
        }
    }
    
    // 3. Check country-specific regulations
    let countryCompliance = try await checkCountryRegulations(
        country: country,
        amount: amount,
        userId: userId
    )
    
    guard countryCompliance.allowed else {
        return ComplianceValidation(
            passed: false,
            reason: countryCompliance.restriction ?? "Payouts not available in your region",
            requiresAction: false,
            actionUrl: nil
        )
    }
    
    // 4. Check for sanctions or watchlists
    let sanctionsCheck = try await checkSanctionsList(userId: userId)
    guard sanctionsCheck.clear else {
        return ComplianceValidation(
            passed: false,
            reason: "Account flagged for compliance review",
            requiresAction: true,
            actionUrl: "/support"
        )
    }
    
    return ComplianceValidation(
        passed: true,
        reason: nil,
        requiresAction: false,
        actionUrl: nil
    )
}

// Usage in payout flow (before component creation or in onPrePayoutSubmit)
let complianceCheck = try await validatePayoutCompliance(
    userId: currentUser.id,
    amount: getCurrentPayoutAmount(),
    country: currentUser.country
)

if !complianceCheck.passed {
    var errorMessage = complianceCheck.reason ?? "Compliance check failed"
    
    if complianceCheck.requiresAction, let actionUrl = complianceCheck.actionUrl {
        // Navigate to action URL
        navigateToCompliance(url: actionUrl)
    } else {
        showError(errorMessage)
    }
    
    return
}

// Proceed with payout

Email format validation

Validate email addresses for payout notifications and records.

func validateEmail(_ email: String) -> Bool {
    let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
    let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
    return emailPredicate.evaluate(with: email)
}

Best practices

Fail early

Validate data before displaying payout components to provide immediate feedback and prevent unnecessary processing.

@objc func requestPayoutButtonTapped() {
    // Validate amount before showing payout components
    let amountValidation = validatePayoutAmount(
        amount: getPayoutAmount(),
        currency: "USD",
        userId: currentUser.id
    )
    
    guard amountValidation.isValid else {
        showAlert(
            title: "Invalid Amount",
            message: amountValidation.errorMessage ?? "Payout amount validation failed"
        )
        return
    }
    
    // Check frequency limits
    let frequencyCheck = try await validatePayoutFrequency(
        userId: currentUser.id,
        requestedAmount: getPayoutAmount()
    )
    
    guard frequencyCheck.isValid else {
        showAlert(
            title: "Payout Limit Reached",
            message: frequencyCheck.errorMessage ?? "Payout frequency limit exceeded"
        )
        return
    }
    
    // Check compliance
    let complianceCheck = try await validatePayoutCompliance(
        userId: currentUser.id,
        amount: getPayoutAmount(),
        country: currentUser.country
    )
    
    guard complianceCheck.passed else {
        handleComplianceFailure(complianceCheck)
        return
    }
    
    // All validations passed - show payout screen
    showPayoutScreen()
}

Provide clear error messages

When validation fails, provide specific, actionable error messages.

config.onError = { error in
    let errorTitle: String
    let errorMessage: String
    let actionButton: String?
    
    switch error.errorCode {
    case "SDK0809":
        errorTitle = "Account Information Missing"
        errorMessage = "PayPal account information is missing. Please try linking your account again."
        actionButton = "Retry"
        
    case "SDK0810":
        errorTitle = "Invalid Amount"
        errorMessage = "The payout amount is invalid. Please contact support for assistance."
        actionButton = "Contact Support"
        
    case "SDK0811":
        errorTitle = "Invalid Amount"
        errorMessage = "Payout amount must be greater than zero. Please check your available balance."
        actionButton = "Check Balance"
        
    case "SDK0812":
        errorTitle = "Missing Amount"
        errorMessage = "Payout amount is required. Please check your configuration."
        actionButton = "Retry"
        
    case "SDK0813", "SDK0814", "SDK0815":
        errorTitle = "Currency Error"
        errorMessage = "Invalid currency configuration. Please contact support."
        actionButton = "Contact Support"
        
    case "SDK0817":
        errorTitle = "Account Error"
        errorMessage = "PayPal account identifier is too long. Please try again."
        actionButton = "Retry"
        
    case "SDK0818":
        errorTitle = "Invalid PayPal Account"
        errorMessage = "Your PayPal account information is invalid. Please link your account again."
        actionButton = "Relink Account"
        
    case "SDK0819":
        errorTitle = "Payout Failed"
        errorMessage = "The payout transaction failed. Please try again or contact support."
        actionButton = "Retry"
        
    default:
        errorTitle = "Payout Failed"
        errorMessage = "An error occurred while processing your payout. Please try again."
        actionButton = "Retry"
    }
    
    showAlert(
        title: errorTitle,
        message: errorMessage,
        primaryButton: actionButton
    )
}

Log validation errors

Log all validation errors for debugging, analytics, and compliance monitoring.

config.onError = { error in
    // Log to analytics
    Analytics.logEvent("payout_validation_error", parameters: [
        "error_code": error.errorCode,
        "error_message": error.errorMessage,
        "user_id": currentUser.id,
        "payout_amount": getCurrentPayoutAmount(),
        "timestamp": Date().timeIntervalSince1970
    ])
    
    // Log to crash reporting
    Crashlytics.crashlytics().record(error: error)
    Crashlytics.crashlytics().setCustomValue(currentUser.id, forKey: "user_id")
    Crashlytics.crashlytics().setCustomValue(getCurrentPayoutAmount(), forKey: "payout_amount")
    
    // Log to server for compliance
    logPayoutErrorToServer(
        userId: currentUser.id,
        errorCode: error.errorCode,
        errorMessage: error.errorMessage,
        amount: getCurrentPayoutAmount(),
        timestamp: Date()
    )
    
    // Console log
    print("Payout error [\(error.errorCode)]: \(error.errorMessage)")
}

Test edge cases

Test validation with edge cases and boundary values.

func testPayoutValidation() async {
    // Test minimum amount
    var validation = try await validatePayoutAmount(amount: 1.0, currency: "USD", userId: "test")
    assert(validation.isValid, "Minimum amount should be valid")
    
    validation = try await validatePayoutAmount(amount: 0.99, currency: "USD", userId: "test")
    assert(!validation.isValid, "Below minimum should be invalid")
    
    // Test maximum amount
    validation = try await validatePayoutAmount(amount: 60000.0, currency: "USD", userId: "test")
    assert(validation.isValid, "Maximum amount should be valid")
    
    validation = try await validatePayoutAmount(amount: 60000.01, currency: "USD", userId: "test")
    assert(!validation.isValid, "Above maximum should be invalid")
    
    // Test zero and negative
    validation = try await validatePayoutAmount(amount: 0.0, currency: "USD", userId: "test")
    assert(!validation.isValid, "Zero amount should be invalid")
    
    validation = try await validatePayoutAmount(amount: -10.0, currency: "USD", userId: "test")
    assert(!validation.isValid, "Negative amount should be invalid")
    
    // Test invalid numbers
    validation = try await validatePayoutAmount(amount: Double.nan, currency: "USD", userId: "test")
    assert(!validation.isValid, "NaN should be invalid")
    
    validation = try await validatePayoutAmount(amount: Double.infinity, currency: "USD", userId: "test")
    assert(!validation.isValid, "Infinity should be invalid")
    
    // Test payer ID validation
    var payerIdCheck = validatePayerId("PAYERID123")
    assert(payerIdCheck.isValid, "Valid payer ID should pass")
    
    payerIdCheck = validatePayerId("")
    assert(!payerIdCheck.isValid, "Empty payer ID should fail")
    
    payerIdCheck = validatePayerId("TOOLONGPAYERID123")
    assert(!payerIdCheck.isValid, "Payer ID over 13 chars should fail")
    
    payerIdCheck = validatePayerId("INVALID@ID")
    assert(!payerIdCheck.isValid, "Non-alphanumeric should fail")
    
    // Test email validation
    assert(validateEmail("user@example.com"), "Valid email should pass")
    assert(!validateEmail("invalid.email"), "Invalid email should fail")
    assert(!validateEmail("@example.com"), "Email without user should fail")
    assert(!validateEmail("user@"), "Email without domain should fail")
    
    print("All payout validation tests passed ✅")
}

Monitor validation metrics

Track validation failure rates to identify issues early.

func trackValidationMetrics(error: BaseSdkException) {
    // Track error distribution
    Analytics.logEvent("payout_error_distribution", parameters: [
        "error_type": error.errorCode,
        "timestamp": Date().timeIntervalSince1970
    ])
    
    // Increment error counter
    incrementErrorCounter(errorCode: error.errorCode)
    
    // Alert if error rate is high
    let errorRate = getRecentErrorRate()
    if errorRate > 0.1 { // More than 10% error rate
        sendAlertToMonitoring(
            message: "High payout error rate detected: \(errorRate * 100)%",
            severity: "high"
        )
    }
}