Skip to content

Instantly share code, notes, and snippets.

@alexbruno
Last active April 23, 2024 13:02
Show Gist options
  • Star 88 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save alexbruno/6623b5afa847f891de9cb6f704d86d02 to your computer and use it in GitHub Desktop.
Save alexbruno/6623b5afa847f891de9cb6f704d86d02 to your computer and use it in GitHub Desktop.
Validação de CNPJ
// Regex para validação de string no formato CNPJ
export const regexCNPJ = /^\d{2}.\d{3}.\d{3}\/\d{4}-\d{2}$/
// Método de validação
// Referência: https://pt.wikipedia.org/wiki/Cadastro_Nacional_da_Pessoa_Jur%C3%ADdica
export function validCNPJ(value: string | number | number[] = '') {
if (!value) return false
// Aceita receber o valor como string, número ou array com todos os dígitos
const isString = typeof value === 'string'
const validTypes = isString || Number.isInteger(value) || Array.isArray(value)
// Elimina valor de tipo inválido
if (!validTypes) return false
// Filtro inicial para entradas do tipo string
if (isString) {
// Teste Regex para veificar se é uma string apenas dígitos válida
const digitsOnly = /^\d{14}$/.test(value)
// Teste Regex para verificar se é uma string formatada válida
const validFormat = regexCNPJ.test(value)
// Verifica se o valor passou em ao menos 1 dos testes
const isValid = digitsOnly || validFormat
// Se o formato não é válido, retorna inválido
if (!isValid) return false
}
// Elimina tudo que não é dígito
const numbers = matchNumbers(value)
// Valida a quantidade de dígitos
if (numbers.length !== 14) return false
// Elimina inválidos com todos os dígitos iguais
const items = [...new Set(numbers)]
if (items.length === 1) return false
// Separa os 2 últimos dígitos verificadores
const digits = numbers.slice(12)
// Valida 1o. dígito verificador
const digit0 = validCalc(12, numbers)
if (digit0 !== digits[0]) return false
// Valida 2o. dígito verificador
const digit1 = validCalc(13, numbers)
return digit1 === digits[1]
}
// Método de formatação
export function formatCNPJ(value: string | number | number[] = '') {
// Verifica se o valor é válido
const valid = validCNPJ(value)
// Se o valor não for válido, retorna vazio
if (!valid) return ''
// Elimina tudo que não é dígito
const numbers = matchNumbers(value)
const text = numbers.join('')
// Formatação do CNPJ: 99.999.999/9999-99
const format = text.replace(
/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/,
'$1.$2.$3/$4-$5',
)
// Retorna o valor formatado
return format
}
// Cálculo validador
function validCalc(x: number, numbers: number[]) {
const slice = numbers.slice(0, x)
let factor = x - 7
let sum = 0
for (let i = x; i >= 1; i--) {
const n = slice[x - i]
sum += n * factor--
if (factor < 2) factor = 9
}
const result = 11 - (sum % 11)
return result > 9 ? 0 : result
}
// Elimina tudo que não é dígito
function matchNumbers(value: string | number | number[] = '') {
const match = value.toString().match(/\d/g)
return Array.isArray(match) ? match.map(Number) : []
}
@Chagall
Copy link

Chagall commented Sep 10, 2020

Salvou minha vida, valeu! xD

@alexbruno
Copy link
Author

Salvou minha vida, valeu! xD

Nem lembrava que tinha isso aqui, realmente muito útil!
Atualizei o script para um código mais moderno e aderente às boas práticas JS atuais.
Fique à vontade para atualizar o seu também.
Aproveitei e fiz um de CPF que também é muito útil.

@naider
Copy link

naider commented Oct 16, 2020

#11 // Guarda um array com todos os dígitos do valor
Se passar uma string sem números acontece um erro e para o código.
Coloquei uma verificação nesse posso para retornar false caso null.

@alexbruno
Copy link
Author

Obrigado pela contribuição @naider !

De fato percebi aqui que o ponto está na linha 12:

const numbers = value.toString().match(/\d/g).map(Number)

Se o método match não consegue achar nenhum dígito na string ele retorna null, quebrando o código porque null não tem um método map.

Vou atualizar o código para corrigir isso.

@naider
Copy link

naider commented Oct 16, 2020

Obrigado pela contribuição @naider !

De fato percebi aqui que o ponto está na linha 12:

const numbers = value.toString().match(/\d/g).map(Number)

Se o método match não consegue achar nenhum dígito na string ele retorna null, quebrando o código porque null não tem um método map.

Vou atualizar o código para corrigir isso.

Eu que agradeço quebrou uma arvore pra mim. =D

@anghello
Copy link

anghello commented Jan 9, 2021

Muito obrigado pela contribuição!

@RafaMoralles
Copy link

Alex, muito obrigado, ajudou muito.

@marciock
Copy link

Salvou a minha vida também,.,,,,

@felipe2g
Copy link

felipe2g commented Apr 22, 2021

Saudações meu amigo, também sou de Uberlândia.

Tive de adicionar uma nova regra abaixo da linha 12 do código para verificar se quando o CNPJ for do tipo string há somente números, pois se digitássemos os dígitos do cnpj seguidos de uma letra o código retornava true.

Como estou usando somente o método por string, acredito que será necessário aplicar para todas as formas de verificação.

  // Se for do tipo string verifica se há somente numeros
  const isnum = /^\d+$/.test(value);
  if (!isnum && typeof value === 'string') return false;

@alexbruno
Copy link
Author

Muito obrigado @felipe2g ! Acho que entendi o problema.

Como o método ignora qualquer caractere que não seja dígito para fazer a validação apenas da quantidade de dígitos e do cálculo, então pode-se passar junto com o valor de um CNPJ válido qualquer outra coisa que será ignorada e o retornará válido.

Vou fazer uns testes assim que puder e aplicar sua sugestão.

Valeu pela contribuição!

@felipe2g
Copy link

Exatamente, o problema é quando usamos a validação pra saber se vamos inserir ou não no banco de dados, como estava retornando true, estava indo os caracteres também para o banco.

Valeu!

@Trouttt
Copy link

Trouttt commented May 19, 2021

Muito obrigado de verdade me salvou também!! Parabéns!!

@alexbruno
Copy link
Author

@felipe2g depois do seu comentário, eu acrescentei uma validação robusta para entradas do tipo string.

Uma validação apenas do tipo "Se for string verifica se há somente números" não seria completa o suficiente, pois não funcionaria para validar um CNPJ válido e formatado no padrão de formatação: XX.XXX.XXX/XXXX-XX.

Então incluí alguns passos extras para validar o formato de entrada de strings. Acredito que tenha ficado bem mais robusto e confiável agora.

Obrigado mais uma vez, ajudou a melhorar o método e todos podem aproveitar!

@josejonathan7
Copy link

e eu agradeço a todos vocês pela contribuição usarei esse método agora e ta quebrando um galho kk

@jorgemustafa
Copy link

Sensacional irmão! Apenas um ponto, da linha 22 até a 24 deu erro aqui em 18/05/2023, então substituí pelo trecho abaixo:
return digitsOnly || validFormat;

@alexbruno
Copy link
Author

alexbruno commented May 19, 2023

Sensacional irmão! Apenas um ponto, da linha 22 até a 24 deu erro aqui em 18/05/2023, então substituí pelo trecho abaixo: return digitsOnly || validFormat;

É @jorgemustafa, já saquei o erro, o código que usei ali na época não é uma boa prática atualmente.

Retornar naquele ponto como você fez não é o suficiente, pois se você passar uma string de CNPJ com formato válido mas que não seja um CNPJ válido de fato segundo o algoritmo de validação, você terá um falso positivo.

Atualizei o código corrigindo o erro. Obrigado pela contribuição!

@gabrielmagevski
Copy link

O HOMEM É BRABOOO!!

@alexbruno
Copy link
Author

Código atualizado:

  • TypeScript para maior clareza dos tipos de dados
  • Separação de funções reutilizáveis para maior clareza do que acontece no geral
  • Bônus: Método de formatação CNPJ

@ssbreno
Copy link

ssbreno commented Jan 5, 2024

Ajustes + Testes com JestJs

import { validCNPJ } from './validate-cnpj';

describe('validCNPJ', () => {
  it('should validate a valid CNPJ', () => {
    expect(validCNPJ('04.470.781/0001-39')).toBeTruthy();
  });

  it('should invalidate an invalid CNPJ', () => {
    expect(validCNPJ('11.111.111/1111-11')).toBeFalsy();
  });

  it('should return false for an invalid CNPJ', () => {
    const cnpj = '34.574.376/0001-93';
    expect(validCNPJ(cnpj)).toBeFalsy();
  });

  it('should return false for a CNPJ with all identical digits', () => {
    const cnpj = '11.111.111/1111-11';
    expect(validCNPJ(cnpj)).toBeFalsy();
  });

  it('should return false for a CNPJ with incorrect length', () => {
    const cnpj = '34.574.376/0001';
    expect(validCNPJ(cnpj)).toBeFalsy();
  });

  it('should return false for a CNPJ with non-numeric characters', () => {
    const cnpj = '34a574b376/0001c92';
    expect(validCNPJ(cnpj)).toBeFalsy();
  });

  it('should return false for an empty string', () => {
    const cnpj = '';
    expect(validCNPJ(cnpj)).toBeFalsy();
  });

  it('should return false for a null value', () => {
    const cnpj = null;
    expect(validCNPJ(cnpj)).toBeFalsy();
  });
});

Os dois primeiros tests quebraram, fiz ajuste na func para funcionar.

atualizei o regex para ficar dessa forma, ficando mas especifica para o metodo.

export const regexCNPJ = /^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/;

export const validCNPJ = (value: string | number | number[] = '') => {
  if (!value) return false;

  const isString = typeof value === 'string';
  if (isString && !regexCNPJ.test(value)) return false;

  const numbers = matchNumbers(value);
  if (numbers.length !== 14) return false;

  const items = [...new Set(numbers)];
  if (items.length === 1) return false;

  const digit0 = validCalc(12, numbers);
  const digit1 = validCalc(13, numbers);

  return digit0 === numbers[12] && digit1 === numbers[13];
};

function validCalc(x: number, numbers: number[]) {
  let factor = x - 7;
  let sum = 0;

  for (let i = 0; i < x; i++) {
    sum += numbers[i] * factor--;
    if (factor < 2) factor = 9;
  }

  const result = 11 - (sum % 11);
  return result > 9 ? 0 : result;
}

function matchNumbers(value: string | number | number[] = '') {
  const match = value
    .toString()
    .replace(/[^\d]+/g, '')
    .match(/\d/g);
  return Array.isArray(match) ? match.map(Number) : [];
}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment