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
3 changes: 1 addition & 2 deletions src/validations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* @returns a boolean value.
*/
export const isRepeated = (ref: string) => {
const ret = ref.replace(new RegExp(ref[0], 'g'), '').trim().length === 0;
return ret;
return /^([A-Z0-9])\1+$/.test(ref);
};

/**
Expand Down
65 changes: 48 additions & 17 deletions src/validations/validateCNPJ.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,71 @@
import { isRepeated } from './utils';

/**
* The function `validateCNPJ` validates a Brazilian CNPJ number.
* @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'.
* @returns The function `validateCNPJ` returns a boolean value. It returns `true` if the CNPJ is
* valid, and `false` otherwise.
* The function `validateCNPJ` validates a Brazilian CNPJ number, supporting both
* the traditional numeric format and the new alphanumeric format introduced by Receita Federal.
*
* @param {string} value - The `value` parameter is a string representing the CNPJ number to be validated.
* It can contain formatting characters (such as '.', '/', '-') and may include letters (A-Z) in the first
* 12 positions, as allowed in the new alphanumeric CNPJ format.
*
* Examples of valid inputs:
* - '00.000.000/0000-00' (numeric format)
* - '00000000000000' (numeric without formatting)
* - 'A1.BC2.34D/56EF-90' (alphanumeric format)
* - 'A1BC234D56EF90' (alphanumeric without formatting)
*
* @returns {boolean} Returns `true` if the CNPJ is valid according to the official
* validation algorithm (modulo 11), or `false` otherwise.
*
* @description
* The validation process includes:
* - Normalizing the input by removing all non-alphanumeric characters and converting to uppercase
* - Verifying that the length is exactly 14 characters
* - Rejecting sequences with all identical characters
* - Converting alphanumeric characters to numeric values (A=10, B=11, ..., Z=35)
* - Calculating the two verification digits using the modulo 11 algorithm
*
* This function is fully compatible with both legacy numeric CNPJs and the new alphanumeric standard.
*/
export function validateCNPJ(value: string): boolean {
const cnpj = String(value).replace(/[^\d]+/g, '');
const clean = (value || "")
.toUpperCase()
.replace(/[^A-Z0-9]/g, "");

if (cnpj === '' || cnpj.length !== 14 || isRepeated(cnpj)) {
if (clean.length !== 14 || isRepeated(clean)) {
return false;
}

const calculateDigit = (numbers: string, weights: number[]): number => {
// Converte caractere para número (0-9 ou A=10 ... Z=35)
const charToNumber = (char: string): number => {
if (/[0-9]/.test(char)) return parseInt(char, 10);
return char.charCodeAt(0) - 55; // A=10, B=11...
};

const calculateDigit = (base: string, weights: number[]): number => {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += parseInt(numbers.charAt(i), 10) * weights[i];

for (let i = 0; i < base.length; i++) {
sum += charToNumber(base[i]) * weights[i];
}

const remainder = sum % 11;
return remainder < 2 ? 0 : 11 - remainder;
};

const base12 = clean.substring(0, 12);

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);
const dv1 = calculateDigit(base12, weights1);

if (dv1 !== parseInt(cnpj.charAt(12), 10)) {
if (dv1 !== charToNumber(clean[12])) {
return false;
}

const base13 = clean.substring(0, 13);

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);
const dv2 = calculateDigit(base13, weights2);

return dv2 === parseInt(cnpj.charAt(13), 10);
return dv2 === charToNumber(clean[13]);
}