UNPKG

react-native-country-documents-validator

Version:

Comprehensive global country document validator for React Native with 155+ countries

1,343 lines 114 kB
"use strict"; /** * Country Validator Utility - Scalable Rule-Based System * * A robust, highly accurate validator for national IDs, passports, and alien cards * across different countries. Uses a rule-based approach for maximum scalability. * * Features: * - High accuracy with official formatting standards * - Rule-based system for easy country additions * - TypeScript support with strong typing * - Lightweight with no external dependencies * - Comprehensive validation including checksums where applicable * - All African countries supported */ Object.defineProperty(exports, "__esModule", { value: true }); exports.validationInputPair = exports.isCountrySupported = exports.registerCountryValidator = exports.getValidationDescription = exports.formatDocument = exports.validateDocument = exports.validateAlienCard = exports.validateNationalId = exports.validatePassport = exports.getSupportedDocumentTypes = exports.getSupportedCountries = exports.COUNTRIES = void 0; // ================================ // UTILITY FUNCTIONS // ================================ /** * Calculates Luhn checksum for validation algorithms */ function calculateLuhnChecksum(digits) { let sum = 0; let alternate = false; for (let i = digits.length - 1; i >= 0; i--) { let n = parseInt(digits.charAt(i), 10); if (alternate) { n *= 2; if (n > 9) { n = (n % 10) + 1; } } sum += n; alternate = !alternate; } return sum % 10; } /** * South African ID checksum validator */ function validateSouthAfricanIdChecksum(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 13) return null; // Extract components const birthDate = cleanId.substring(0, 6); const citizenship = cleanId.substring(10, 11); const checkDigit = parseInt(cleanId.substring(12, 13), 10); // Validate birth date (YYMMDD) const month = parseInt(birthDate.substring(2, 4), 10); const day = parseInt(birthDate.substring(4, 6), 10); if (month < 1 || month > 12) { return { isValid: false, errorMessage: "Invalid month in South African ID", }; } if (day < 1 || day > 31) { return { isValid: false, errorMessage: "Invalid day in South African ID" }; } // Validate citizenship indicator (0 = SA citizen, 1 = permanent resident) if (citizenship !== "0" && citizenship !== "1") { return { isValid: false, errorMessage: "Invalid citizenship indicator in South African ID", }; } // Validate checksum using Luhn algorithm const checksumDigits = cleanId.substring(0, 12); const calculatedChecksum = calculateLuhnChecksum(checksumDigits); const expectedCheckDigit = calculatedChecksum === 0 ? 0 : 10 - calculatedChecksum; if (checkDigit !== expectedCheckDigit) { return { isValid: false, errorMessage: "Invalid checksum in South African ID", }; } return { isValid: true, normalizedValue: cleanId }; } /** * Nigerian NIN pattern validator */ function validateNigerianNinPattern(nin) { const cleanNin = nin.replace(/[\s-]/g, ""); if (cleanNin.length !== 11) return null; // NIN cannot start with 0 if (cleanNin.charAt(0) === "0") { return { isValid: false, errorMessage: "Nigerian NIN cannot start with 0" }; } // Basic pattern validation - ensure it's not sequential or repeated const digits = cleanNin.split("").map(Number); const isSequential = digits.every((digit, index) => index === 0 || digit === digits[index - 1] + 1); const isRepeated = digits.every((digit) => digit === digits[0]); if (isSequential || isRepeated) { return { isValid: false, errorMessage: "Invalid Nigerian NIN pattern" }; } return { isValid: true, normalizedValue: cleanNin }; } /** * Chinese National ID checksum validator (18 digits with check digit) */ function validateChineseIdChecksum(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 18) return null; // Extract birth date (YYYYMMDD) - positions 6-13 const birthYear = parseInt(cleanId.substring(6, 10), 10); const birthMonth = parseInt(cleanId.substring(10, 12), 10); const birthDay = parseInt(cleanId.substring(12, 14), 10); // Validate birth date if (birthYear < 1900 || birthYear > new Date().getFullYear()) { return { isValid: false, errorMessage: "Invalid birth year in Chinese ID" }; } if (birthMonth < 1 || birthMonth > 12) { return { isValid: false, errorMessage: "Invalid birth month in Chinese ID", }; } if (birthDay < 1 || birthDay > 31) { return { isValid: false, errorMessage: "Invalid birth day in Chinese ID" }; } // Validate checksum using Chinese algorithm const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; const checkDigits = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]; let sum = 0; for (let i = 0; i < 17; i++) { sum += parseInt(cleanId.charAt(i), 10) * weights[i]; } const expectedCheck = checkDigits[sum % 11]; const actualCheck = cleanId.charAt(17).toUpperCase(); if (actualCheck !== expectedCheck) { return { isValid: false, errorMessage: "Invalid checksum in Chinese ID" }; } return { isValid: true, normalizedValue: cleanId }; } /** * Indian Aadhaar validator (12 digits with Verhoeff checksum) */ function validateIndianAadhaarChecksum(aadhaar) { const cleanAadhaar = aadhaar.replace(/[\s-]/g, ""); if (cleanAadhaar.length !== 12) return null; // Aadhaar cannot start with 0 or 1 if (cleanAadhaar.charAt(0) === "0" || cleanAadhaar.charAt(0) === "1") { return { isValid: false, errorMessage: "Aadhaar cannot start with 0 or 1" }; } // Simplified checksum validation (actual Verhoeff is complex) // Basic pattern check to avoid obvious fakes const digits = cleanAadhaar.split("").map(Number); const isRepeated = digits.every((digit) => digit === digits[0]); const isSequential = digits.every((digit, index) => index === 0 || digit === digits[index - 1] + 1); if (isRepeated || isSequential) { return { isValid: false, errorMessage: "Invalid Aadhaar pattern" }; } return { isValid: true, normalizedValue: cleanAadhaar }; } /** * Thai National ID checksum validator (13 digits with mod-11 checksum) */ function validateThaiIdChecksum(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 13) return null; // Calculate checksum using Thai algorithm let sum = 0; for (let i = 0; i < 12; i++) { sum += parseInt(cleanId.charAt(i), 10) * (13 - i); } const remainder = sum % 11; const expectedCheck = remainder < 2 ? remainder : 11 - remainder; const actualCheck = parseInt(cleanId.charAt(12), 10); if (actualCheck !== expectedCheck) { return { isValid: false, errorMessage: "Invalid Thai ID checksum" }; } return { isValid: true, normalizedValue: cleanId }; } /** * Malaysian MyKad format validator (YYMMDD-PB-###G) */ function validateMalaysianMyKadFormat(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 12) return null; // Extract birth date (YYMMDD) - first 6 digits const year = parseInt(cleanId.substring(0, 2), 10); const month = parseInt(cleanId.substring(2, 4), 10); const day = parseInt(cleanId.substring(4, 6), 10); // Validate month and day if (month < 1 || month > 12) { return { isValid: false, errorMessage: "Invalid birth month in Malaysian MyKad", }; } if (day < 1 || day > 31) { return { isValid: false, errorMessage: "Invalid birth day in Malaysian MyKad", }; } return { isValid: true, normalizedValue: cleanId }; } /** * German ID checksum validator (11 digits with mod-11 checksum) */ function validateGermanIdChecksum(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 11) return null; // Extract check digit (last digit) const checkDigit = parseInt(cleanId.charAt(10), 10); const digits = cleanId.substring(0, 10); // Calculate checksum using German algorithm let sum = 0; for (let i = 0; i < 10; i++) { sum += parseInt(digits.charAt(i), 10) * (i + 1); } const calculatedCheck = sum % 11; const expectedCheck = calculatedCheck === 10 ? 0 : calculatedCheck; if (checkDigit !== expectedCheck) { return { isValid: false, errorMessage: "Invalid German ID checksum" }; } return { isValid: true, normalizedValue: cleanId }; } /** * Swedish personal number validator (YYYYMMDD-XXXX with Luhn checksum) */ function validateSwedishPersonalNumber(id) { const cleanId = id.replace(/[\s-]/g, ""); if (cleanId.length !== 12) return null; // Extract birth date (YYYYMMDD) const year = parseInt(cleanId.substring(0, 4), 10); const month = parseInt(cleanId.substring(4, 6), 10); const day = parseInt(cleanId.substring(6, 8), 10); // Validate birth date if (year < 1900 || year > new Date().getFullYear()) { return { isValid: false, errorMessage: "Invalid birth year in Swedish ID" }; } if (month < 1 || month > 12) { return { isValid: false, errorMessage: "Invalid birth month in Swedish ID", }; } if (day < 1 || day > 31) { return { isValid: false, errorMessage: "Invalid birth day in Swedish ID" }; } // Luhn checksum validation on last 4 digits const lastFour = cleanId.substring(8); const checkDigit = parseInt(lastFour.charAt(3), 10); const digits = lastFour.substring(0, 3); let sum = 0; for (let i = 0; i < 3; i++) { let digit = parseInt(digits.charAt(i), 10); if (i % 2 === 0) { digit *= 2; if (digit > 9) digit = Math.floor(digit / 10) + (digit % 10); } sum += digit; } const calculatedCheck = (10 - (sum % 10)) % 10; if (checkDigit !== calculatedCheck) { return { isValid: false, errorMessage: "Invalid Swedish ID checksum" }; } return { isValid: true, normalizedValue: cleanId }; } /** * Italian Codice Fiscale validator (complex algorithm) */ function validateItalianCodiceFiscale(cf) { const cleanCf = cf.replace(/[\s-]/g, "").toUpperCase(); if (cleanCf.length !== 16) return null; // Pattern: 6 letters + 2 digits + 1 letter + 2 digits + 1 letter + 3 chars + 1 check if (!/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/.test(cleanCf)) { return { isValid: false, errorMessage: "Invalid Italian Codice Fiscale format", }; } // Checksum calculation (simplified) const oddValues = { "0": 1, "1": 0, "2": 5, "3": 7, "4": 9, "5": 13, "6": 15, "7": 17, "8": 19, "9": 21, A: 1, B: 0, C: 5, D: 7, E: 9, F: 13, G: 15, H: 17, I: 19, J: 21, K: 2, L: 4, M: 18, N: 20, O: 11, P: 3, Q: 6, R: 8, S: 12, T: 14, U: 16, V: 10, W: 22, X: 25, Y: 24, Z: 23, }; const evenValues = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9, K: 10, L: 11, M: 12, N: 13, O: 14, P: 15, Q: 16, R: 17, S: 18, T: 19, U: 20, V: 21, W: 22, X: 23, Y: 24, Z: 25, }; let sum = 0; for (let i = 0; i < 15; i++) { const char = cleanCf.charAt(i); if (i % 2 === 0) { sum += oddValues[char] || 0; } else { sum += evenValues[char] || 0; } } const checkLetter = String.fromCharCode(65 + (sum % 26)); const actualCheck = cleanCf.charAt(15); if (checkLetter !== actualCheck) { return { isValid: false, errorMessage: "Invalid Italian Codice Fiscale checksum", }; } return { isValid: true, normalizedValue: cleanCf }; } /** * UK National Insurance Number validator */ function validateUkNiNumber(nino) { const cleanNino = nino.replace(/[\s-]/g, "").toUpperCase(); if (cleanNino.length !== 9) return null; // Pattern: 2 letters + 6 digits + 1 letter if (!/^[A-Z]{2}\d{6}[A-Z]$/.test(cleanNino)) { return { isValid: false, errorMessage: "Invalid UK NINO format" }; } // Check invalid prefixes const invalidPrefixes = ["BG", "GB", "NK", "KN", "TN", "NT", "ZZ"]; const prefix = cleanNino.substring(0, 2); if (invalidPrefixes.includes(prefix)) { return { isValid: false, errorMessage: "Invalid UK NINO prefix" }; } // Check invalid letters const invalidLetters = ["D", "F", "I", "Q", "U", "V"]; if (invalidLetters.includes(cleanNino.charAt(0)) || invalidLetters.includes(cleanNino.charAt(1))) { return { isValid: false, errorMessage: "Invalid letters in UK NINO" }; } return { isValid: true, normalizedValue: cleanNino }; } /** * French Social Security Number validator (INSEE number) */ function validateFrenchInseeNumber(insee) { const cleanInsee = insee.replace(/[\s-]/g, ""); if (cleanInsee.length !== 15) return null; // Pattern: 13 digits + 2 check digits if (!/^\d{15}$/.test(cleanInsee)) { return { isValid: false, errorMessage: "Invalid French INSEE format" }; } // Extract components const gender = cleanInsee.charAt(0); const year = cleanInsee.substring(1, 3); const month = cleanInsee.substring(3, 5); // Validate gender (1-2 for male, female) if (!["1", "2"].includes(gender)) { return { isValid: false, errorMessage: "Invalid gender in French INSEE" }; } // Validate month const monthNum = parseInt(month, 10); if (monthNum < 1 || monthNum > 12) { return { isValid: false, errorMessage: "Invalid month in French INSEE" }; } // Calculate checksum (mod 97) const baseNumber = cleanInsee.substring(0, 13); const checkDigits = parseInt(cleanInsee.substring(13), 10); const calculatedCheck = 97 - (parseInt(baseNumber, 10) % 97); if (checkDigits !== calculatedCheck) { return { isValid: false, errorMessage: "Invalid French INSEE checksum" }; } return { isValid: true, normalizedValue: cleanInsee }; } /** * Canadian Social Insurance Number (SIN) validator */ function validateCanadianSin(sin) { const cleanSin = sin.replace(/[\s-]/g, ""); if (cleanSin.length !== 9) return null; // SIN cannot start with 0, 8, or 9 const firstDigit = cleanSin.charAt(0); if (firstDigit === "0" || firstDigit === "8" || firstDigit === "9") { return { isValid: false, errorMessage: "Canadian SIN cannot start with 0, 8, or 9", }; } // Luhn algorithm validation let sum = 0; for (let i = 0; i < 9; i++) { let digit = parseInt(cleanSin.charAt(i), 10); // Double every second digit (positions 1, 3, 5, 7) if (i % 2 === 1) { digit *= 2; // If result is two digits, add them together if (digit > 9) { digit = Math.floor(digit / 10) + (digit % 10); } } sum += digit; } if (sum % 10 !== 0) { return { isValid: false, errorMessage: "Invalid Canadian SIN checksum" }; } return { isValid: true, normalizedValue: cleanSin }; } /** * Mexican CURP validator (Clave Única de Registro de Población) */ function validateMexicanCurp(curp) { const cleanCurp = curp.replace(/[\s-]/g, "").toUpperCase(); if (cleanCurp.length !== 18) return null; // Pattern: 4 letters + 6 digits (YYMMDD) + 1 letter + 5 chars + 1 check digit if (!/^[A-Z]{4}\d{6}[HM][A-Z]{2}[A-Z0-9]{3}[A-Z0-9]$/.test(cleanCurp)) { return { isValid: false, errorMessage: "Invalid Mexican CURP format", }; } // Extract birth date const year = parseInt(cleanCurp.substring(4, 6), 10); const month = parseInt(cleanCurp.substring(6, 8), 10); const day = parseInt(cleanCurp.substring(8, 10), 10); // Validate birth date if (month < 1 || month > 12) { return { isValid: false, errorMessage: "Invalid birth month in Mexican CURP", }; } if (day < 1 || day > 31) { return { isValid: false, errorMessage: "Invalid birth day in Mexican CURP", }; } // Gender validation (H = male, M = female) const gender = cleanCurp.charAt(10); if (gender !== "H" && gender !== "M") { return { isValid: false, errorMessage: "Invalid gender in Mexican CURP" }; } return { isValid: true, normalizedValue: cleanCurp }; } /** * US Social Security Number validator */ function validateUsSsn(ssn) { const cleanSsn = ssn.replace(/[\s-]/g, ""); if (cleanSsn.length !== 9) return null; // Cannot be all zeros or follow invalid patterns if (cleanSsn === "000000000") { return { isValid: false, errorMessage: "US SSN cannot be all zeros" }; } // Area number (first 3 digits) cannot be 000, 666, or 900-999 const area = parseInt(cleanSsn.substring(0, 3), 10); if (area === 0 || area === 666 || area >= 900) { return { isValid: false, errorMessage: "Invalid US SSN area number" }; } // Group number (middle 2 digits) cannot be 00 const group = cleanSsn.substring(3, 5); if (group === "00") { return { isValid: false, errorMessage: "Invalid US SSN group number" }; } // Serial number (last 4 digits) cannot be 0000 const serial = cleanSsn.substring(5); if (serial === "0000") { return { isValid: false, errorMessage: "Invalid US SSN serial number" }; } return { isValid: true, normalizedValue: cleanSsn }; } /** * Brazilian CPF validator (Cadastro de Pessoas Físicas) */ function validateBrazilianCpf(cpf) { const cleanCpf = cpf.replace(/[\s.-]/g, ""); if (cleanCpf.length !== 11) return null; // Check for invalid patterns (all same digits) if (/^(\d)\1{10}$/.test(cleanCpf)) { return { isValid: false, errorMessage: "Brazilian CPF cannot have all identical digits", }; } // Calculate first check digit let sum = 0; for (let i = 0; i < 9; i++) { sum += parseInt(cleanCpf.charAt(i), 10) * (10 - i); } let remainder = sum % 11; const firstCheck = remainder < 2 ? 0 : 11 - remainder; if (parseInt(cleanCpf.charAt(9), 10) !== firstCheck) { return { isValid: false, errorMessage: "Invalid Brazilian CPF first check digit", }; } // Calculate second check digit sum = 0; for (let i = 0; i < 10; i++) { sum += parseInt(cleanCpf.charAt(i), 10) * (11 - i); } remainder = sum % 11; const secondCheck = remainder < 2 ? 0 : 11 - remainder; if (parseInt(cleanCpf.charAt(10), 10) !== secondCheck) { return { isValid: false, errorMessage: "Invalid Brazilian CPF second check digit", }; } return { isValid: true, normalizedValue: cleanCpf }; } /** * Chilean RUT validator (Rol Único Tributario) */ function validateChileanRut(rut) { const cleanRut = rut.replace(/[\s.-]/g, "").toUpperCase(); if (cleanRut.length < 8 || cleanRut.length > 9) return null; // Extract number and check digit const number = cleanRut.slice(0, -1); const checkDigit = cleanRut.slice(-1); // Validate that number part contains only digits if (!/^\d+$/.test(number)) { return { isValid: false, errorMessage: "Chilean RUT number must contain only digits", }; } // Calculate check digit using mod-11 algorithm let sum = 0; let multiplier = 2; for (let i = number.length - 1; i >= 0; i--) { sum += parseInt(number.charAt(i), 10) * multiplier; multiplier = multiplier === 7 ? 2 : multiplier + 1; } const remainder = sum % 11; const expectedCheck = remainder === 0 ? "0" : remainder === 1 ? "K" : (11 - remainder).toString(); if (checkDigit !== expectedCheck) { return { isValid: false, errorMessage: "Invalid Chilean RUT check digit" }; } return { isValid: true, normalizedValue: cleanRut }; } /** * Colombian Cedula validator (basic pattern validation) */ function validateColombianCedula(cedula) { const cleanCedula = cedula.replace(/[\s.-]/g, ""); if (cleanCedula.length < 7 || cleanCedula.length > 10) return null; // Basic validation - cannot start with 0 unless it's a short number if (cleanCedula.length > 7 && cleanCedula.charAt(0) === "0") { return { isValid: false, errorMessage: "Colombian Cedula cannot start with 0 for numbers > 7 digits", }; } return { isValid: true, normalizedValue: cleanCedula }; } /** * Argentine DNI validator (basic validation) */ function validateArgentineDni(dni) { const cleanDni = dni.replace(/[\s.-]/g, ""); if (cleanDni.length < 7 || cleanDni.length > 8) return null; // DNI cannot be all zeros or sequential if (cleanDni === "00000000" || cleanDni === "0000000") { return { isValid: false, errorMessage: "Argentine DNI cannot be all zeros", }; } // Check for sequential numbers (simple pattern) const digits = cleanDni.split("").map(Number); const isSequential = digits.every((digit, index) => index === 0 || digit === digits[index - 1] + 1); if (isSequential) { return { isValid: false, errorMessage: "Argentine DNI cannot be sequential", }; } return { isValid: true, normalizedValue: cleanDni }; } /** * Generic validator that applies rules to a document value */ function validateWithRule(value, rule) { if (!value || typeof value !== "string") { return { isValid: false, errorMessage: "Document number is required" }; } const cleanValue = value.replace(/[\s-]/g, "").toUpperCase(); // Check custom validator first if (rule.customValidator) { const customResult = rule.customValidator(cleanValue); if (customResult) return customResult; } // Check exact length if (rule.exactLength && cleanValue.length !== rule.exactLength) { return { isValid: false, errorMessage: `${rule.description} must be exactly ${rule.exactLength} characters`, }; } // Check min/max length if (rule.minLength && cleanValue.length < rule.minLength) { return { isValid: false, errorMessage: `${rule.description} must be at least ${rule.minLength} characters`, }; } if (rule.maxLength && cleanValue.length > rule.maxLength) { return { isValid: false, errorMessage: `${rule.description} must be at most ${rule.maxLength} characters`, }; } // Check prefix if specified if (rule.prefix && rule.prefix.length > 0) { const hasValidPrefix = rule.prefix.some((prefix) => cleanValue.startsWith(prefix)); if (!hasValidPrefix) { return { isValid: false, errorMessage: `${rule.description} must start with ${rule.prefix.join(" or ")}`, }; } } // Check pattern if (!rule.pattern.test(cleanValue)) { return { isValid: false, errorMessage: `Invalid ${rule.description} format`, }; } return { isValid: true, normalizedValue: cleanValue, }; } // ================================ // COUNTRIES REGISTRY // ================================ exports.COUNTRIES = { // East Africa KE: { alpha2: "KE", alpha3: "KEN", name: "Kenya" }, UG: { alpha2: "UG", alpha3: "UGA", name: "Uganda" }, TZ: { alpha2: "TZ", alpha3: "TZA", name: "Tanzania" }, RW: { alpha2: "RW", alpha3: "RWA", name: "Rwanda" }, BI: { alpha2: "BI", alpha3: "BDI", name: "Burundi" }, ET: { alpha2: "ET", alpha3: "ETH", name: "Ethiopia" }, SO: { alpha2: "SO", alpha3: "SOM", name: "Somalia" }, DJ: { alpha2: "DJ", alpha3: "DJI", name: "Djibouti" }, ER: { alpha2: "ER", alpha3: "ERI", name: "Eritrea" }, SS: { alpha2: "SS", alpha3: "SSD", name: "South Sudan" }, // West Africa NG: { alpha2: "NG", alpha3: "NGA", name: "Nigeria" }, GH: { alpha2: "GH", alpha3: "GHA", name: "Ghana" }, SN: { alpha2: "SN", alpha3: "SEN", name: "Senegal" }, CI: { alpha2: "CI", alpha3: "CIV", name: "Ivory Coast" }, ML: { alpha2: "ML", alpha3: "MLI", name: "Mali" }, BF: { alpha2: "BF", alpha3: "BFA", name: "Burkina Faso" }, NE: { alpha2: "NE", alpha3: "NER", name: "Niger" }, GN: { alpha2: "GN", alpha3: "GIN", name: "Guinea" }, SL: { alpha2: "SL", alpha3: "SLE", name: "Sierra Leone" }, LR: { alpha2: "LR", alpha3: "LBR", name: "Liberia" }, TG: { alpha2: "TG", alpha3: "TGO", name: "Togo" }, BJ: { alpha2: "BJ", alpha3: "BEN", name: "Benin" }, GM: { alpha2: "GM", alpha3: "GMB", name: "Gambia" }, GW: { alpha2: "GW", alpha3: "GNB", name: "Guinea-Bissau" }, CV: { alpha2: "CV", alpha3: "CPV", name: "Cape Verde" }, // Central Africa CM: { alpha2: "CM", alpha3: "CMR", name: "Cameroon" }, CF: { alpha2: "CF", alpha3: "CAF", name: "Central African Republic" }, TD: { alpha2: "TD", alpha3: "TCD", name: "Chad" }, CG: { alpha2: "CG", alpha3: "COG", name: "Republic of the Congo" }, CD: { alpha2: "CD", alpha3: "COD", name: "Democratic Republic of the Congo" }, GA: { alpha2: "GA", alpha3: "GAB", name: "Gabon" }, GQ: { alpha2: "GQ", alpha3: "GNQ", name: "Equatorial Guinea" }, ST: { alpha2: "ST", alpha3: "STP", name: "Sao Tome and Principe" }, // Southern Africa ZA: { alpha2: "ZA", alpha3: "ZAF", name: "South Africa" }, BW: { alpha2: "BW", alpha3: "BWA", name: "Botswana" }, ZM: { alpha2: "ZM", alpha3: "ZMB", name: "Zambia" }, ZW: { alpha2: "ZW", alpha3: "ZWE", name: "Zimbabwe" }, NA: { alpha2: "NA", alpha3: "NAM", name: "Namibia" }, LS: { alpha2: "LS", alpha3: "LSO", name: "Lesotho" }, SZ: { alpha2: "SZ", alpha3: "SWZ", name: "Eswatini" }, MW: { alpha2: "MW", alpha3: "MWI", name: "Malawi" }, MZ: { alpha2: "MZ", alpha3: "MOZ", name: "Mozambique" }, MG: { alpha2: "MG", alpha3: "MDG", name: "Madagascar" }, MU: { alpha2: "MU", alpha3: "MUS", name: "Mauritius" }, SC: { alpha2: "SC", alpha3: "SYC", name: "Seychelles" }, KM: { alpha2: "KM", alpha3: "COM", name: "Comoros" }, // North Africa EG: { alpha2: "EG", alpha3: "EGY", name: "Egypt" }, LY: { alpha2: "LY", alpha3: "LBY", name: "Libya" }, TN: { alpha2: "TN", alpha3: "TUN", name: "Tunisia" }, DZ: { alpha2: "DZ", alpha3: "DZA", name: "Algeria" }, MA: { alpha2: "MA", alpha3: "MAR", name: "Morocco" }, SD: { alpha2: "SD", alpha3: "SDN", name: "Sudan" }, // Asia - East Asia CN: { alpha2: "CN", alpha3: "CHN", name: "China" }, JP: { alpha2: "JP", alpha3: "JPN", name: "Japan" }, KR: { alpha2: "KR", alpha3: "KOR", name: "South Korea" }, TW: { alpha2: "TW", alpha3: "TWN", name: "Taiwan" }, HK: { alpha2: "HK", alpha3: "HKG", name: "Hong Kong" }, MO: { alpha2: "MO", alpha3: "MAC", name: "Macau" }, MN: { alpha2: "MN", alpha3: "MNG", name: "Mongolia" }, // Asia - Southeast Asia ID: { alpha2: "ID", alpha3: "IDN", name: "Indonesia" }, MY: { alpha2: "MY", alpha3: "MYS", name: "Malaysia" }, SG: { alpha2: "SG", alpha3: "SGP", name: "Singapore" }, TH: { alpha2: "TH", alpha3: "THA", name: "Thailand" }, PH: { alpha2: "PH", alpha3: "PHL", name: "Philippines" }, VN: { alpha2: "VN", alpha3: "VNM", name: "Vietnam" }, MM: { alpha2: "MM", alpha3: "MMR", name: "Myanmar" }, KH: { alpha2: "KH", alpha3: "KHM", name: "Cambodia" }, LA: { alpha2: "LA", alpha3: "LAO", name: "Laos" }, BN: { alpha2: "BN", alpha3: "BRN", name: "Brunei" }, TL: { alpha2: "TL", alpha3: "TLS", name: "East Timor" }, // Asia - South Asia IN: { alpha2: "IN", alpha3: "IND", name: "India" }, PK: { alpha2: "PK", alpha3: "PAK", name: "Pakistan" }, BD: { alpha2: "BD", alpha3: "BGD", name: "Bangladesh" }, LK: { alpha2: "LK", alpha3: "LKA", name: "Sri Lanka" }, NP: { alpha2: "NP", alpha3: "NPL", name: "Nepal" }, BT: { alpha2: "BT", alpha3: "BTN", name: "Bhutan" }, MV: { alpha2: "MV", alpha3: "MDV", name: "Maldives" }, AF: { alpha2: "AF", alpha3: "AFG", name: "Afghanistan" }, // Asia - Central Asia KZ: { alpha2: "KZ", alpha3: "KAZ", name: "Kazakhstan" }, UZ: { alpha2: "UZ", alpha3: "UZB", name: "Uzbekistan" }, TM: { alpha2: "TM", alpha3: "TKM", name: "Turkmenistan" }, KG: { alpha2: "KG", alpha3: "KGZ", name: "Kyrgyzstan" }, TJ: { alpha2: "TJ", alpha3: "TJK", name: "Tajikistan" }, // Asia - Western Asia (Middle East) SA: { alpha2: "SA", alpha3: "SAU", name: "Saudi Arabia" }, AE: { alpha2: "AE", alpha3: "ARE", name: "United Arab Emirates" }, IR: { alpha2: "IR", alpha3: "IRN", name: "Iran" }, TR: { alpha2: "TR", alpha3: "TUR", name: "Turkey" }, IQ: { alpha2: "IQ", alpha3: "IRQ", name: "Iraq" }, IL: { alpha2: "IL", alpha3: "ISR", name: "Israel" }, JO: { alpha2: "JO", alpha3: "JOR", name: "Jordan" }, LB: { alpha2: "LB", alpha3: "LBN", name: "Lebanon" }, SY: { alpha2: "SY", alpha3: "SYR", name: "Syria" }, YE: { alpha2: "YE", alpha3: "YEM", name: "Yemen" }, OM: { alpha2: "OM", alpha3: "OMN", name: "Oman" }, QA: { alpha2: "QA", alpha3: "QAT", name: "Qatar" }, KW: { alpha2: "KW", alpha3: "KWT", name: "Kuwait" }, BH: { alpha2: "BH", alpha3: "BHR", name: "Bahrain" }, PS: { alpha2: "PS", alpha3: "PSE", name: "Palestine" }, CY: { alpha2: "CY", alpha3: "CYP", name: "Cyprus" }, GE: { alpha2: "GE", alpha3: "GEO", name: "Georgia" }, AM: { alpha2: "AM", alpha3: "ARM", name: "Armenia" }, AZ: { alpha2: "AZ", alpha3: "AZE", name: "Azerbaijan" }, // Europe - Western Europe GB: { alpha2: "GB", alpha3: "GBR", name: "United Kingdom" }, FR: { alpha2: "FR", alpha3: "FRA", name: "France" }, DE: { alpha2: "DE", alpha3: "DEU", name: "Germany" }, IT: { alpha2: "IT", alpha3: "ITA", name: "Italy" }, ES: { alpha2: "ES", alpha3: "ESP", name: "Spain" }, NL: { alpha2: "NL", alpha3: "NLD", name: "Netherlands" }, BE: { alpha2: "BE", alpha3: "BEL", name: "Belgium" }, CH: { alpha2: "CH", alpha3: "CHE", name: "Switzerland" }, AT: { alpha2: "AT", alpha3: "AUT", name: "Austria" }, PT: { alpha2: "PT", alpha3: "PRT", name: "Portugal" }, IE: { alpha2: "IE", alpha3: "IRL", name: "Ireland" }, LU: { alpha2: "LU", alpha3: "LUX", name: "Luxembourg" }, MC: { alpha2: "MC", alpha3: "MCO", name: "Monaco" }, // Europe - Nordic Countries SE: { alpha2: "SE", alpha3: "SWE", name: "Sweden" }, NO: { alpha2: "NO", alpha3: "NOR", name: "Norway" }, DK: { alpha2: "DK", alpha3: "DNK", name: "Denmark" }, FI: { alpha2: "FI", alpha3: "FIN", name: "Finland" }, IS: { alpha2: "IS", alpha3: "ISL", name: "Iceland" }, // Europe - Eastern Europe PL: { alpha2: "PL", alpha3: "POL", name: "Poland" }, CZ: { alpha2: "CZ", alpha3: "CZE", name: "Czech Republic" }, SK: { alpha2: "SK", alpha3: "SVK", name: "Slovakia" }, HU: { alpha2: "HU", alpha3: "HUN", name: "Hungary" }, RO: { alpha2: "RO", alpha3: "ROU", name: "Romania" }, BG: { alpha2: "BG", alpha3: "BGR", name: "Bulgaria" }, UA: { alpha2: "UA", alpha3: "UKR", name: "Ukraine" }, BY: { alpha2: "BY", alpha3: "BLR", name: "Belarus" }, RU: { alpha2: "RU", alpha3: "RUS", name: "Russia" }, MD: { alpha2: "MD", alpha3: "MDA", name: "Moldova" }, // Europe - Baltic States EE: { alpha2: "EE", alpha3: "EST", name: "Estonia" }, LV: { alpha2: "LV", alpha3: "LVA", name: "Latvia" }, LT: { alpha2: "LT", alpha3: "LTU", name: "Lithuania" }, // Europe - Balkans HR: { alpha2: "HR", alpha3: "HRV", name: "Croatia" }, RS: { alpha2: "RS", alpha3: "SRB", name: "Serbia" }, SI: { alpha2: "SI", alpha3: "SVN", name: "Slovenia" }, BA: { alpha2: "BA", alpha3: "BIH", name: "Bosnia and Herzegovina" }, ME: { alpha2: "ME", alpha3: "MNE", name: "Montenegro" }, MK: { alpha2: "MK", alpha3: "MKD", name: "North Macedonia" }, AL: { alpha2: "AL", alpha3: "ALB", name: "Albania" }, XK: { alpha2: "XK", alpha3: "XKX", name: "Kosovo" }, // Europe - Mediterranean GR: { alpha2: "GR", alpha3: "GRC", name: "Greece" }, MT: { alpha2: "MT", alpha3: "MLT", name: "Malta" }, // Europe - Microstates LI: { alpha2: "LI", alpha3: "LIE", name: "Liechtenstein" }, AD: { alpha2: "AD", alpha3: "AND", name: "Andorra" }, SM: { alpha2: "SM", alpha3: "SMR", name: "San Marino" }, VA: { alpha2: "VA", alpha3: "VAT", name: "Vatican City" }, // North America US: { alpha2: "US", alpha3: "USA", name: "United States" }, CA: { alpha2: "CA", alpha3: "CAN", name: "Canada" }, MX: { alpha2: "MX", alpha3: "MEX", name: "Mexico" }, GT: { alpha2: "GT", alpha3: "GTM", name: "Guatemala" }, BZ: { alpha2: "BZ", alpha3: "BLZ", name: "Belize" }, SV: { alpha2: "SV", alpha3: "SLV", name: "El Salvador" }, HN: { alpha2: "HN", alpha3: "HND", name: "Honduras" }, NI: { alpha2: "NI", alpha3: "NIC", name: "Nicaragua" }, CR: { alpha2: "CR", alpha3: "CRI", name: "Costa Rica" }, PA: { alpha2: "PA", alpha3: "PAN", name: "Panama" }, CU: { alpha2: "CU", alpha3: "CUB", name: "Cuba" }, JM: { alpha2: "JM", alpha3: "JAM", name: "Jamaica" }, HT: { alpha2: "HT", alpha3: "HTI", name: "Haiti" }, DO: { alpha2: "DO", alpha3: "DOM", name: "Dominican Republic" }, BS: { alpha2: "BS", alpha3: "BHS", name: "The Bahamas" }, BB: { alpha2: "BB", alpha3: "BRB", name: "Barbados" }, LC: { alpha2: "LC", alpha3: "LCA", name: "Saint Lucia" }, GD: { alpha2: "GD", alpha3: "GRD", name: "Grenada" }, VC: { alpha2: "VC", alpha3: "VCT", name: "Saint Vincent and the Grenadines" }, AG: { alpha2: "AG", alpha3: "ATG", name: "Antigua and Barbuda" }, DM: { alpha2: "DM", alpha3: "DMA", name: "Dominica" }, KN: { alpha2: "KN", alpha3: "KNA", name: "Saint Kitts and Nevis" }, TT: { alpha2: "TT", alpha3: "TTO", name: "Trinidad and Tobago" }, // South America BR: { alpha2: "BR", alpha3: "BRA", name: "Brazil" }, AR: { alpha2: "AR", alpha3: "ARG", name: "Argentina" }, CL: { alpha2: "CL", alpha3: "CHL", name: "Chile" }, CO: { alpha2: "CO", alpha3: "COL", name: "Colombia" }, PE: { alpha2: "PE", alpha3: "PER", name: "Peru" }, VE: { alpha2: "VE", alpha3: "VEN", name: "Venezuela" }, EC: { alpha2: "EC", alpha3: "ECU", name: "Ecuador" }, BO: { alpha2: "BO", alpha3: "BOL", name: "Bolivia" }, PY: { alpha2: "PY", alpha3: "PRY", name: "Paraguay" }, UY: { alpha2: "UY", alpha3: "URY", name: "Uruguay" }, GY: { alpha2: "GY", alpha3: "GUY", name: "Guyana" }, SR: { alpha2: "SR", alpha3: "SUR", name: "Suriname" }, GF: { alpha2: "GF", alpha3: "GUF", name: "French Guiana" }, }; // ================================ // VALIDATION RULES // ================================ const VALIDATION_RULES = { // East Africa KE: { country: exports.COUNTRIES.KE, rules: { nationalId: { pattern: /^\d{6,8}$/, minLength: 6, maxLength: 8, description: "Kenya National ID", }, passport: { pattern: /^[AB][A-Z0-9]{6,8}$/, minLength: 7, maxLength: 9, prefix: ["A", "B"], description: "Kenya Passport", customValidator: (value) => { const digitCount = (value.match(/\d/g) || []).length; if (digitCount < 5) { return { isValid: false, errorMessage: "Kenya passport must contain at least 5 digits", }; } return null; }, }, alienCard: { pattern: /^\d{6,9}$/, minLength: 6, maxLength: 9, description: "Kenya Alien Card", }, }, }, UG: { country: exports.COUNTRIES.UG, rules: { nationalId: { pattern: /^[A-Z0-9]{14}$/, exactLength: 14, description: "Uganda National ID", }, passport: { pattern: /^[B][A-Z0-9]{7}$/, exactLength: 8, prefix: ["B"], description: "Uganda Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Uganda Alien Card", }, }, }, TZ: { country: exports.COUNTRIES.TZ, rules: { nationalId: { pattern: /^\d{20}$/, exactLength: 20, description: "Tanzania National ID", }, passport: { pattern: /^[AB][A-Z0-9]{7}$/, exactLength: 8, prefix: ["A", "B"], description: "Tanzania Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Tanzania Alien Card", }, }, }, RW: { country: exports.COUNTRIES.RW, rules: { nationalId: { pattern: /^\d{16}$/, exactLength: 16, description: "Rwanda National ID", }, passport: { pattern: /^[P][A-Z0-9]{7}$/, exactLength: 8, prefix: ["P"], description: "Rwanda Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Rwanda Alien Card", }, }, }, ET: { country: exports.COUNTRIES.ET, rules: { nationalId: { pattern: /^[A-Z0-9]{10,15}$/, minLength: 10, maxLength: 15, description: "Ethiopia National ID", }, passport: { pattern: /^[E][A-Z0-9]{7}$/, exactLength: 8, prefix: ["E"], description: "Ethiopia Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Ethiopia Alien Card", }, }, }, // West Africa NG: { country: exports.COUNTRIES.NG, rules: { nationalId: { pattern: /^\d{11}$/, exactLength: 11, description: "Nigeria NIN", customValidator: validateNigerianNinPattern, format: (value) => { const clean = value.replace(/[\s-]/g, ""); if (clean.length === 11) { return `${clean.substring(0, 5)}-${clean.substring(5)}`; } return clean; }, }, passport: { pattern: /^[ABC]\d{8}$/, exactLength: 9, prefix: ["A", "B", "C"], description: "Nigeria Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Nigeria Alien Card", }, }, }, GH: { country: exports.COUNTRIES.GH, rules: { nationalId: { pattern: /^[A-Z]{3}-\d{9}-\d$/, exactLength: 15, description: "Ghana National ID", }, passport: { pattern: /^[G]\d{7}$/, exactLength: 8, prefix: ["G"], description: "Ghana Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Ghana Alien Card", }, }, }, SN: { country: exports.COUNTRIES.SN, rules: { nationalId: { pattern: /^\d{12}$/, exactLength: 12, description: "Senegal National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Senegal Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Senegal Alien Card", }, }, }, CI: { country: exports.COUNTRIES.CI, rules: { nationalId: { pattern: /^\d{12}$/, exactLength: 12, description: "Ivory Coast National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Ivory Coast Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Ivory Coast Alien Card", }, }, }, // Central Africa CM: { country: exports.COUNTRIES.CM, rules: { nationalId: { pattern: /^[A-Z0-9]{10,15}$/, minLength: 10, maxLength: 15, description: "Cameroon National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Cameroon Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Cameroon Alien Card", }, }, }, // Southern Africa ZA: { country: exports.COUNTRIES.ZA, rules: { nationalId: { pattern: /^\d{13}$/, exactLength: 13, description: "South Africa ID", customValidator: validateSouthAfricanIdChecksum, format: (value) => { const clean = value.replace(/[\s-]/g, ""); if (clean.length === 13) { return `${clean.substring(0, 6)}-${clean.substring(6, 10)}-${clean.substring(10)}`; } return clean; }, }, passport: { pattern: /^[A-Z][A-Z0-9]{8}$/, exactLength: 9, description: "South Africa Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "South Africa Alien Card", }, }, }, BW: { country: exports.COUNTRIES.BW, rules: { nationalId: { pattern: /^\d{9}$/, exactLength: 9, description: "Botswana National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Botswana Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Botswana Alien Card", }, }, }, ZM: { country: exports.COUNTRIES.ZM, rules: { nationalId: { pattern: /^\d{11}$/, exactLength: 11, description: "Zambia National ID", }, passport: { pattern: /^[Z]\d{7}$/, exactLength: 8, prefix: ["Z"], description: "Zambia Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Zambia Alien Card", }, }, }, ZW: { country: exports.COUNTRIES.ZW, rules: { nationalId: { pattern: /^\d{8}[A-Z]{2}$/, exactLength: 10, description: "Zimbabwe National ID", }, passport: { pattern: /^[AB]\d{7}$/, exactLength: 8, prefix: ["A", "B"], description: "Zimbabwe Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Zimbabwe Alien Card", }, }, }, // North Africa EG: { country: exports.COUNTRIES.EG, rules: { nationalId: { pattern: /^\d{14}$/, exactLength: 14, description: "Egypt National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Egypt Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Egypt Alien Card", }, }, }, TN: { country: exports.COUNTRIES.TN, rules: { nationalId: { pattern: /^\d{8}$/, exactLength: 8, description: "Tunisia National ID", }, passport: { pattern: /^[A-Z0-9]{8}$/, exactLength: 8, description: "Tunisia Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Tunisia Alien Card", }, }, }, DZ: { country: exports.COUNTRIES.DZ, rules: { nationalId: { pattern: /^\d{18}$/, exactLength: 18, description: "Algeria National ID", }, passport: { pattern: /^[A-Z0-9]{9}$/, exactLength: 9, description: "Algeria Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Algeria Alien Card", }, }, }, MA: { country: exports.COUNTRIES.MA, rules: { nationalId: { pattern: /^[A-Z]{1,2}\d{6,8}$/, minLength: 7, maxLength: 10, description: "Morocco National ID", }, passport: { pattern: /^[A-Z0-9]{8,9}$/, minLength: 8, maxLength: 9, description: "Morocco Passport", }, alienCard: { pattern: /^\d{8,12}$/, minLength: 8, maxLength: 12, description: "Morocco Alien