/** * Input validation utilities for user inputs and transaction data */ import { validateBrazilianTaxId } from './validation'; export interface ValidationResult { valid: boolean; errors: string[]; warnings: string[]; } /** * Validate transaction amount */ export function validateAmount(amount: number | string): ValidationResult { const errors: string[] = []; const warnings: string[] = []; const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount; if (isNaN(numAmount)) { errors.push('Amount must be a valid number'); } else if (numAmount <= 0) { errors.push('Amount must be greater than zero'); } else if (numAmount > 1000000000) { warnings.push('Amount exceeds 1 billion - please verify'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate currency code (ISO 4217) */ export function validateCurrency(currency: string): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!currency || currency.trim().length === 0) { errors.push('Currency code is required'); } else if (currency.length !== 3) { errors.push('Currency code must be 3 characters (ISO 4217)'); } else if (!/^[A-Z]{3}$/.test(currency)) { errors.push('Currency code must be uppercase letters only'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate email address */ export function validateEmail(email: string | undefined): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!email) { return { valid: true, errors, warnings }; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { errors.push('Invalid email address format'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate phone number (basic validation) */ export function validatePhone(phone: string | undefined): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!phone) { return { valid: true, errors, warnings }; } const phoneRegex = /^[\d\s\-\+\(\)]+$/; if (!phoneRegex.test(phone)) { errors.push('Invalid phone number format'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate account number or IBAN */ export function validateAccountNumber( accountNumber: string | undefined, iban: string | undefined ): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!accountNumber && !iban) { errors.push('Either account number or IBAN is required'); return { valid: false, errors, warnings }; } if (iban) { // Basic IBAN validation (2 letters + 2 digits + up to 30 alphanumeric) const ibanRegex = /^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/; if (!ibanRegex.test(iban.replace(/\s/g, ''))) { errors.push('Invalid IBAN format'); } } if (accountNumber && accountNumber.trim().length === 0) { errors.push('Account number cannot be empty'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate purpose of payment */ export function validatePurposeOfPayment( purpose: string | undefined ): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!purpose || purpose.trim().length === 0) { errors.push('Purpose of payment is required'); } else if (purpose.trim().length < 5) { warnings.push('Purpose of payment is very short - provide more detail'); } else if (purpose.length > 140) { warnings.push('Purpose of payment exceeds 140 characters - may be truncated'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate name field */ export function validateName(name: string | undefined, fieldName: string): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!name || name.trim().length === 0) { errors.push(`${fieldName} is required`); } else if (name.trim().length < 2) { errors.push(`${fieldName} must be at least 2 characters`); } else if (name.length > 140) { warnings.push(`${fieldName} exceeds 140 characters`); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate address */ export function validateAddress( address: string | undefined, city: string | undefined, country: string | undefined ): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!address && !city) { errors.push('Either address or city is required'); } if (!country) { errors.push('Country is required'); } else if (country.length !== 2) { errors.push('Country must be a 2-letter ISO code'); } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate tax ID (CPF or CNPJ) */ export function validateTaxId(taxId: string | undefined, fieldName: string): ValidationResult { const errors: string[] = []; const warnings: string[] = []; if (!taxId) { errors.push(`${fieldName} is required`); return { valid: false, errors, warnings }; } const validation = validateBrazilianTaxId(taxId); if (!validation.valid) { errors.push(`${fieldName}: ${validation.error || 'Invalid format'}`); } return { valid: validation.valid, errors, warnings, }; } /** * Sanitize string input (remove dangerous characters) */ export function sanitizeString(input: string): string { return input .replace(/[<>]/g, '') // Remove potential HTML/XML tags .replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters .trim(); } /** * Sanitize number input */ export function sanitizeNumber(input: string | number): number { if (typeof input === 'number') { return isNaN(input) ? 0 : input; } const num = parseFloat(input.replace(/[^\d.-]/g, '')); return isNaN(num) ? 0 : num; }