Learn about built-in validation and implement additional scenarios for payouts.
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.
This validation guide applies to the payout submission component (PayoutSubmissionComponentConfig) for withdrawal flows.
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.
The PayPal payout submission component returns structured error codes accessible via error.errorCode.
| Error code | Description |
|---|---|
SDK0809 / PayoutPayerIdRequiredException | The payer ID is empty or not provided. The payer ID must be provided in PayPalWallet configuration. |
SDK0810 / PayoutAmountInvalidException | The payout amount is NaN (Not a Number) or infinite. Check the transaction amount configuration. |
SDK0811 / PayoutAmountNotPositiveException | The payout amount must be greater than zero. Negative or zero amounts aren't allowed. |
SDK0812 / PayoutAmountRequiredException | The payout amount is required but wasn't provided in the transaction data. |
SDK0813 / PayoutCurrencyRequiredException | The currency code is required. Must be a non-empty string. |
SDK0814 / PayoutCurrencyLengthInvalidException | The currency code must be exactly 3 characters (ISO 4217 format, e.g., "USD", "EUR"). |
SDK0815 / PayoutCurrencyCodeInvalidException | Invalid currency code. Must be a valid ISO 4217 currency code. |
SDK0816 / PayoutEmailMaxLengthException | The email address exceeds maximum length of 127 characters. |
SDK0817 / PayoutPayerIdMaxLengthException | The payer ID exceeds the maximum length of 13 characters. |
SDK0818 / PayoutPayerIdInvalidException | The payer ID contains invalid characters. Only alphanumeric characters (A-Z, a-z, 0-9) are allowed. |
SDK0819 / PayoutFailedException | The payout transaction failed. Check the error message for details. |
SDK0803 / PaypalPayoutReceiverRequiredException | The receiver is required. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent). |
SDK0804 / PayoutReceiverTypeInvalidException | Invalid receiver type. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent). |
SDK0805 / PaypalPayoutReceiverTypeEmailOnlyException | The receiver type must be Email. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent). |
SDK0806 / PayoutWalletInvalidException | Invalid wallet value. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent). |
SDK0807 / PayoutWalletRequiresEmailException | The wallet requires an email receiver type. Only applies to direct payout components (PayoutAmountComponent, PayPalPayoutReceiverComponent, PayoutSubmissionComponent). |
SDK0808 / PaypalPayoutEmailInvalidException | Invalid 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.
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 allows you to implement business-specific rules, compliance checks, and fraud prevention before executing payouts.
Before component creation:
validatePayoutAmount(): Check amount limits before showing the UI.validatePayoutFrequency(): Check daily/weekly/monthly limits before allowing payout requestvalidatePayoutCompliance(): 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.
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.
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
}
}
)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 creationValidate 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 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)
}
)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 creationCheck 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 payoutValidate 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)
}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()
}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 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 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 ✅")
}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"
)
}
}