react-native-country-documents-validator
Version:
Comprehensive global country document validator for React Native with 155+ countries
1,343 lines • 114 kB
JavaScript
"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