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) : []
}
@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