package utils import ( "regexp" "strings" ) // DocumentValidator provides flexible document validation for global use type DocumentValidator struct { // Country code to use for validation (empty = accept all formats) CountryCode string } // NewDocumentValidator creates a new validator func NewDocumentValidator(countryCode string) *DocumentValidator { return &DocumentValidator{CountryCode: strings.ToUpper(countryCode)} } // ValidationResult represents the result of document validation type ValidationResult struct { Valid bool Message string Clean string // Cleaned document number } // ValidateDocument validates a document based on country // For a global portal, this supports multiple formats func (v *DocumentValidator) ValidateDocument(doc string, docType string) ValidationResult { // Remove all non-alphanumeric characters for cleaning clean := regexp.MustCompile(`[^a-zA-Z0-9]`).ReplaceAllString(doc, "") if clean == "" { return ValidationResult{Valid: true, Message: "Documento opcional não fornecido", Clean: ""} } switch v.CountryCode { case "BR": return v.validateBrazil(clean, docType) case "JP": return v.validateJapan(clean, docType) case "US": return v.validateUSA(clean, docType) default: // For global/unknown countries, accept any alphanumeric if len(clean) < 5 || len(clean) > 30 { return ValidationResult{Valid: false, Message: "Documento deve ter entre 5 e 30 caracteres", Clean: clean} } return ValidationResult{Valid: true, Message: "Documento aceito", Clean: clean} } } // validateBrazil validates Brazilian documents (CNPJ/CPF) func (v *DocumentValidator) validateBrazil(doc string, docType string) ValidationResult { switch strings.ToUpper(docType) { case "CNPJ": if len(doc) != 14 { return ValidationResult{Valid: false, Message: "CNPJ deve ter 14 dígitos", Clean: doc} } if !validateCNPJ(doc) { return ValidationResult{Valid: false, Message: "CNPJ inválido", Clean: doc} } return ValidationResult{Valid: true, Message: "CNPJ válido", Clean: doc} case "CPF": if len(doc) != 11 { return ValidationResult{Valid: false, Message: "CPF deve ter 11 dígitos", Clean: doc} } if !validateCPF(doc) { return ValidationResult{Valid: false, Message: "CPF inválido", Clean: doc} } return ValidationResult{Valid: true, Message: "CPF válido", Clean: doc} default: // Unknown Brazilian document type, accept if reasonable length if len(doc) < 11 || len(doc) > 14 { return ValidationResult{Valid: false, Message: "Documento brasileiro deve ter entre 11 e 14 dígitos", Clean: doc} } return ValidationResult{Valid: true, Message: "Documento aceito", Clean: doc} } } // validateCNPJ validates Brazilian CNPJ using checksum algorithm func validateCNPJ(cnpj string) bool { if len(cnpj) != 14 { return false } // Check for known invalid patterns if cnpj == "00000000000000" || cnpj == "11111111111111" || cnpj == "22222222222222" || cnpj == "33333333333333" || cnpj == "44444444444444" || cnpj == "55555555555555" || cnpj == "66666666666666" || cnpj == "77777777777777" || cnpj == "88888888888888" || cnpj == "99999999999999" { return false } // Calculate first check digit weights1 := []int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2} sum := 0 for i, w := range weights1 { sum += int(cnpj[i]-'0') * w } remainder := sum % 11 checkDigit1 := 0 if remainder >= 2 { checkDigit1 = 11 - remainder } if int(cnpj[12]-'0') != checkDigit1 { return false } // Calculate second check digit weights2 := []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2} sum = 0 for i, w := range weights2 { sum += int(cnpj[i]-'0') * w } remainder = sum % 11 checkDigit2 := 0 if remainder >= 2 { checkDigit2 = 11 - remainder } return int(cnpj[13]-'0') == checkDigit2 } // validateCPF validates Brazilian CPF using checksum algorithm func validateCPF(cpf string) bool { if len(cpf) != 11 { return false } // Check for known invalid patterns if cpf == "00000000000" || cpf == "11111111111" || cpf == "22222222222" || cpf == "33333333333" || cpf == "44444444444" || cpf == "55555555555" || cpf == "66666666666" || cpf == "77777777777" || cpf == "88888888888" || cpf == "99999999999" { return false } // Calculate first check digit sum := 0 for i := 0; i < 9; i++ { sum += int(cpf[i]-'0') * (10 - i) } remainder := sum % 11 checkDigit1 := 0 if remainder >= 2 { checkDigit1 = 11 - remainder } if int(cpf[9]-'0') != checkDigit1 { return false } // Calculate second check digit sum = 0 for i := 0; i < 10; i++ { sum += int(cpf[i]-'0') * (11 - i) } remainder = sum % 11 checkDigit2 := 0 if remainder >= 2 { checkDigit2 = 11 - remainder } return int(cpf[10]-'0') == checkDigit2 } // validateJapan validates Japanese corporate numbers func (v *DocumentValidator) validateJapan(doc string, docType string) ValidationResult { // Japanese Corporate Number (法人番号) is 13 digits if len(doc) == 13 { return ValidationResult{Valid: true, Message: "法人番号 válido", Clean: doc} } // Accept other formats loosely if len(doc) >= 5 && len(doc) <= 20 { return ValidationResult{Valid: true, Message: "Documento aceito", Clean: doc} } return ValidationResult{Valid: false, Message: "Documento japonês inválido", Clean: doc} } // validateUSA validates US documents (EIN) func (v *DocumentValidator) validateUSA(doc string, docType string) ValidationResult { // EIN is 9 digits if strings.ToUpper(docType) == "EIN" { if len(doc) != 9 { return ValidationResult{Valid: false, Message: "EIN must be 9 digits", Clean: doc} } return ValidationResult{Valid: true, Message: "EIN válido", Clean: doc} } // Accept other formats loosely if len(doc) >= 5 && len(doc) <= 20 { return ValidationResult{Valid: true, Message: "Document accepted", Clean: doc} } return ValidationResult{Valid: false, Message: "Invalid US document", Clean: doc} }