diff --git a/src/__tests__/usevalidationsbr.spec.ts b/src/__tests__/usevalidationsbr.spec.ts index c16d551..a6b855a 100644 --- a/src/__tests__/usevalidationsbr.spec.ts +++ b/src/__tests__/usevalidationsbr.spec.ts @@ -14,13 +14,16 @@ describe('UseValidationBr Cep', () => { describe('UseValidationBr CNPJ', () => { it('should be able return true to valid CNPJ', () => { expect(useValidationsBR('cnpj', '66.919.381/0001-15')).toBe(true); + expect(useValidationsBR('cnpj', '12.ABC.345/01DE-35')).toBe(true); }); it('should be able return false to invalid CNPJ', () => { - expect.assertions(2); + expect.assertions(4); expect(useValidationsBR('cnpj', '12.732.455/0001-25')).toBe(false); expect(useValidationsBR('cnpj', '66.919.381/0001-10')).toBe(false); + expect(useValidationsBR('cnpj', '00000000000192')).toBe(false); + expect(useValidationsBR('cnpj', 'ABCDEFGHIJKL81')).toBe(false); }); it('should be able return false to pass a empty string', () => { diff --git a/src/__tests__/validateCNPJ.spec.ts b/src/__tests__/validateCNPJ.spec.ts index a6ba706..29781e9 100644 --- a/src/__tests__/validateCNPJ.spec.ts +++ b/src/__tests__/validateCNPJ.spec.ts @@ -4,6 +4,19 @@ import { validateCNPJ } from '../validations/validateCNPJ'; describe('validateCNPJ', () => { it('should return true for valid CNPJs', () => { expect(validateCNPJ('66.919.381/0001-15')).toBe(true); + expect(validateCNPJ('12.ABC.345/01DE-35')).toBe(true); + expect(validateCNPJ('90.021.382/0001-22')).toBe(true); + expect(validateCNPJ('90.024.778/0001-23')).toBe(true); + expect(validateCNPJ('90.025.108/0001-21')).toBe(true); + expect(validateCNPJ('90.025.255/0001-00')).toBe(true); + expect(validateCNPJ('90.024.420/0001-09')).toBe(true); + expect(validateCNPJ('90.024.781/0001-47')).toBe(true); + expect(validateCNPJ('04.740.714/0001-97')).toBe(true); + expect(validateCNPJ('44.108.058/0001-29')).toBe(true); + expect(validateCNPJ('90.024.780/0001-00')).toBe(true); + expect(validateCNPJ('90.024.779/0001-78')).toBe(true); + expect(validateCNPJ('00000000000191')).toBe(true); + expect(validateCNPJ('ABCDEFGHIJKL80')).toBe(true); }); it('should return false for invalid CNPJs', () => { @@ -11,14 +24,28 @@ describe('validateCNPJ', () => { expect(validateCNPJ('48514588000170')).toBe(false); expect(validateCNPJ('12.732.455/0001-25')).toBe(false); expect(validateCNPJ('66.919.381/0001-10')).toBe(false); - expect(validateCNPJ('123')).toBe(false); + expect(validateCNPJ('00000000000192')).toBe(false); + expect(validateCNPJ('ABCDEFGHIJKL81')).toBe(false); }); it('should return false for empty or null strings', () => { expect(validateCNPJ('')).toBe(false); }); + it('should return false for invalid characters', () => { + expect(validateCNPJ("'!@#$%&*-_=+^~")).toBe(false); + expect(validateCNPJ('$0123456789ABC')).toBe(false); + expect(validateCNPJ('0123456?789ABC')).toBe(false); + expect(validateCNPJ('0123456789ABC#')).toBe(false); + }); + + it('should return false for letter in DV position', () => { + expect(validateCNPJ('0000000000019L')).toBe(false); + expect(validateCNPJ('000000000001P1')).toBe(false); + }); + it('should return false for CNPJs with all digits the same', () => { + expect(validateCNPJ('00000000000000')).toBe(false); expect(validateCNPJ('11.111.111/1111-11')).toBe(false); expect(validateCNPJ('22222222222222')).toBe(false); }); @@ -28,5 +55,6 @@ describe('validateCNPJ', () => { expect(validateCNPJ('66.919.381/0001-155')).toBe(false); expect(validateCNPJ('6691938100011')).toBe(false); expect(validateCNPJ('669193810001155')).toBe(false); + expect(validateCNPJ('123')).toBe(false); }); }); diff --git a/src/validations/validateCNPJ.ts b/src/validations/validateCNPJ.ts index ac23ee8..1a7fcd8 100644 --- a/src/validations/validateCNPJ.ts +++ b/src/validations/validateCNPJ.ts @@ -1,4 +1,58 @@ -import { isRepeated } from './utils'; +/** + * Reference: https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj + */ + +const cnpjSizeWithoutDv: number = 12; +const cnpjRegexWithoutDv: RegExp = /^([A-Z\d]){12}$/; +const cnpjRegex: RegExp = /^([A-Z\d]){12}(\d){2}$/; +const maskCharactersRegex: RegExp = /[./-]/g; +const notAllowedCharactersRegex: RegExp = /[^A-Z\d./-]/i; +const baseValue: number = '0'.charCodeAt(0); +const dvWeights: number[] = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; +const zeroedCnpj: string = '00000000000000'; + +function removeCnpjMask(cnpj: string): string { + return cnpj.replace(maskCharactersRegex, ''); +} + +function calcCnpjDv(cnpj: string): string { + if (!notAllowedCharactersRegex.test(cnpj)) { + const cnpjSemMascara = removeCnpjMask(cnpj); + if ( + cnpjRegexWithoutDv.test(cnpjSemMascara) && + cnpjSemMascara !== zeroedCnpj.substring(0, cnpjSizeWithoutDv) + ) { + let somatorioDV1 = 0; + let somatorioDV2 = 0; + for (let i = 0; i < cnpjSizeWithoutDv; i++) { + const asciiDigito = cnpjSemMascara.charCodeAt(i) - baseValue; + somatorioDV1 += asciiDigito * dvWeights[i + 1]; + somatorioDV2 += asciiDigito * dvWeights[i]; + } + const dv1 = somatorioDV1 % 11 < 2 ? 0 : 11 - (somatorioDV1 % 11); + somatorioDV2 += dv1 * dvWeights[cnpjSizeWithoutDv]; + const dv2 = somatorioDV2 % 11 < 2 ? 0 : 11 - (somatorioDV2 % 11); + return `${dv1}${dv2}`; + } + } + throw new Error( + 'Não é possível calcular o DV pois o CNPJ fornecido é inválido', + ); +} + +function isValidCnpj(cnpj: string): boolean { + if (!notAllowedCharactersRegex.test(cnpj)) { + const unmaskedCnpj = removeCnpjMask(cnpj); + if (cnpjRegex.test(unmaskedCnpj) && unmaskedCnpj !== zeroedCnpj) { + const givenDv = unmaskedCnpj.substring(cnpjSizeWithoutDv); + const calculatedDv = calcCnpjDv( + unmaskedCnpj.substring(0, cnpjSizeWithoutDv), + ); + return givenDv === calculatedDv; + } + } + return false; +} /** * The function `validateCNPJ` validates a Brazilian CNPJ number. @@ -9,32 +63,5 @@ import { isRepeated } from './utils'; * valid, and `false` otherwise. */ export function validateCNPJ(value: string): boolean { - const cnpj = String(value).replace(/[^\d]+/g, ''); - - if (cnpj === '' || cnpj.length !== 14 || isRepeated(cnpj)) { - return false; - } - - const calculateDigit = (numbers: string, weights: number[]): number => { - let sum = 0; - for (let i = 0; i < numbers.length; i++) { - sum += parseInt(numbers.charAt(i), 10) * weights[i]; - } - const remainder = sum % 11; - return remainder < 2 ? 0 : 11 - remainder; - }; - - const weights1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - const first12 = cnpj.substring(0, 12); - const dv1 = calculateDigit(first12, weights1); - - if (dv1 !== parseInt(cnpj.charAt(12), 10)) { - return false; - } - - const weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; - const first13 = cnpj.substring(0, 13); - const dv2 = calculateDigit(first13, weights2); - - return dv2 === parseInt(cnpj.charAt(13), 10); + return isValidCnpj(value); }