A função
Uma só função valida o CNPJ numérico e o alfanumérico. O segredo é que cada caractere entra no módulo 11 como ord - 48: assim '0'..'9' viram 0..9 (retrocompatível) e 'A'..'Z' viram 17..42.
W1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
W2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
def dv(base, w)
s = w.each_with_index.sum { |p, i| (base[i].ord - 48) * p }
r = s % 11
r < 2 ? 0 : 11 - r
end
def valid_cnpj?(cnpj)
cnpj = cnpj.gsub(%r{[./-]}, '').upcase
return false unless cnpj.match?(/\A[A-Z0-9]{12}\d{2}\z/)
return false if cnpj.chars.uniq.length == 1 # repetidos
d1 = dv(cnpj[0, 12], W1)
d2 = dv(cnpj[0, 12] + d1.to_s, W2)
d1 == cnpj[12].to_i && d2 == cnpj[13].to_i
end
valid_cnpj?('12.ABC.345/01DE-35') # => true (alfanumérico, exemplo SERPRO)
valid_cnpj?('11.222.333/0001-81') # => true (numérico)
valid_cnpj?('11.222.333/0001-00') # => falseO mesmo código valida os dois formatos: o numérico é um caso particular do alfanumérico. A base tem 12 posições (letras A–Z ou dígitos 0–9) e os 2 verificadores são sempre numéricos.
Por que ord - 48
No módulo 11 cada posição vira um número antes de multiplicar pelo peso. Para tratar letra e dígito de forma uniforme, converte-se o caractere pelo seu código ASCII menos 48: '0' vira 0, '9' vira 9 e 'A' vira 17 (segue até 'Z' = 42). É exatamente o que base[i].ord - 48 faz dentro de dv.
Os pesos vêm em duas listas (W1 para o 1º dígito, W2 para o 2º) e o resto da divisão por 11 define o verificador — r < 2 ? 0 : 11 - r. É o módulo 11 de sempre, só com a entrada normalizada.
Por que o exemplo SERPRO confere
A base oficial 12ABC34501DE produz DV 35: no 1º dígito a soma dá 459 (resto 8 → 11 - 8 = 3) e no 2º a soma dá 424 (resto 6 → 11 - 6 = 5). A função acima reproduz exatamente isso. Detalhe do cálculo em calcular o dígito verificador alfanumérico.
Cuidados
- Só maiúsculas. O alfanumérico não aceita minúsculas — por isso a função faz
.upcaseantes de validar. - Os DV são sempre numéricos. A regex exige
\d{2}no fim: letra nos dois últimos é inválido. - Sequências repetidas (
'00000000000000') passariam no módulo 11, por isso a função as descarta antes comcnpj.chars.uniq.length == 1. - No banco, use texto. Colunas numéricas quebram com letras — guarde em
CHAR(14)/VARCHAR. - Válido ≠ existe. A função confirma a matemática, não se a empresa está cadastrada na Receita. Para isso é outra coisa: CNPJ válido vs. CNPJ real.
Continue
Perguntas frequentes
O mesmo código valida o CNPJ alfanumérico?
valid_cnpj? converte cada caractere por ord − 48, então '0'..'9' viram 0..9 (retrocompatível) e 'A'..'Z' entram no mesmo módulo 11. O numérico é um caso particular do alfanumérico.Os dígitos verificadores podem ser letras?
\d{2}.Preciso de alguma gem?
cpf_cnpj (de fnando) já suporta o formato alfanumérico; brazilian_documents (de fidelisrafael) é outra opção.