Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/__tests__/usevalidationsbr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,29 @@ describe('UseValidationBr Cep', () => {

describe('UseValidationBr CNPJ', () => {
it('should be able return true to valid CNPJ', () => {
expect.assertions(2);

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(3);

expect(useValidationsBR('cnpj', '12.732.455/0001-25')).toBe(false);
expect(useValidationsBR('cnpj', '66.919.381/0001-10')).toBe(false);
expect(useValidationsBR('cnpj', '12.ABC.345/01DE-34')).toBe(false);
});

it('should be able return false to pass a empty string', () => {
expect(useValidationsBR('cnpj', '')).toBe(false);
});

it('should be able return false to pass a string over 14 characters', () => {
expect.assertions(2);

expect(useValidationsBR('cnpj', '66.919.381/00001-14')).toBe(false);
expect(useValidationsBR('cnpj', '12.ABC.345/001DE-35')).toBe(false);
});

it('should be able return false to pass a only repeated numbers', () => {
Expand Down
15 changes: 14 additions & 1 deletion src/__tests__/validateCNPJ.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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('12ABC34501DE35')).toBe(true);
});

it('should return false for invalid CNPJs', () => {
Expand All @@ -12,21 +14,32 @@ describe('validateCNPJ', () => {
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('12.ABC.345/01DE-34')).toBe(false);
expect(validateCNPJ('VQ.A8K.XRA/0001-49')).toBe(false);
expect(validateCNPJ('12abc34501de35')).toBe(false);
expect(validateCNPJ('49YYDYRJ000168')).toBe(false);
expect(validateCNPJ('12ABC34501DEAB')).toBe(false);
});

it('should return false for empty or null strings', () => {
expect(validateCNPJ('')).toBe(false);
});

it('should return false for CNPJs with all digits the same', () => {
it('should return false for CNPJs with all characters the same', () => {
expect(validateCNPJ('11.111.111/1111-11')).toBe(false);
expect(validateCNPJ('22222222222222')).toBe(false);
expect(validateCNPJ('BB.BBB.BBB/BBBB-BB')).toBe(false);
expect(validateCNPJ('AAAAAAAAAAAAAA')).toBe(false);
});

it('should return false for CNPJs with incorrect length', () => {
expect(validateCNPJ('66.919.381/0001-1')).toBe(false);
expect(validateCNPJ('66.919.381/0001-155')).toBe(false);
expect(validateCNPJ('6691938100011')).toBe(false);
expect(validateCNPJ('669193810001155')).toBe(false);
expect(validateCNPJ('12.ABC.345/01DE-3')).toBe(false);
expect(validateCNPJ('12.ABC.345/01DE-355')).toBe(false);
expect(validateCNPJ('12ABC34501DE3')).toBe(false);
expect(validateCNPJ('12ABC34501DE355')).toBe(false);
});
});
77 changes: 46 additions & 31 deletions src/validations/validateCNPJ.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,55 @@
import { isRepeated } from './utils';

/**
* The function `validateCNPJ` validates a Brazilian CNPJ number.
* The function `charToNumber` converts a CNPJ character into the number used by the
* check digit calculation. Following the Receita Federal spec, the ASCII value of the
* character is offset by 48, so digits map to themselves ('0' -> 0) and uppercase
* letters to their code ('A' -> 17).
* @param {string} char - A single character from the CNPJ base.
* @returns The numeric value used in the weighted checksum.
*/
function charToNumber(char: string): number {
return char.charCodeAt(0) - 48;
}

/**
* The function `generateChecksum` calculates a checksum based on a given base and a weight
* array. Each character in the `base` is converted with `charToNumber` and multiplied by the
* corresponding weight at the same index.
* @param {string} base - The value for which the checksum is calculated.
* @param {number[]} weight - The weights applied to each character of the base.
* @returns The calculated MOD 11 check digit.
*/
function generateChecksum(base: string, weight: number[]): number {
const sum = base
.split('')
.reduce((acc, char, i) => acc + charToNumber(char) * weight[i], 0);
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 weights2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];

/**
* The function `validateCNPJ` validates a Brazilian CNPJ number, supporting both the
* traditional numeric format and the alphanumeric format (12 uppercase alphanumeric
* positions followed by 2 numeric check digits).
* @param {string} value - The `value` parameter is a string that represents the CNPJ (Cadastro
* Nacional da Pessoa Jurídica) number to be validated. It can be formatted with or without punctuation,
* like '00.000.000/0000-00' or '00000000000000'.
* Nacional da Pessoa Jurídica) number to be validated. It can be formatted with or without
* punctuation, like '00.000.000/0000-00', '00000000000000' or '12.ABC.345/01DE-35'.
* @returns The function `validateCNPJ` returns a boolean value. It returns `true` if the CNPJ is
* 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);
const cnpj = String(value).replace(/[./-]/g, '');

if (!/^[A-Z\d]{12}\d{2}$/.test(cnpj) || isRepeated(cnpj)) return false;

const base = cnpj.substring(0, 12);
const dv1 = generateChecksum(base, weights1);
const dv2 = generateChecksum(base + dv1, weights2);

return `${dv1}${dv2}` === cnpj.substring(12);
}