Validar, gerar e formatar CNPJ em Delphi
Unit em Delphi para validar, gerar e formatar CNPJ numérico e alfanumérico com Ord(c) - 48, testes com DUnitX e consulta à API com TNetHTTPClient.
Delphi ainda move muito ERP, sistema fiscal e automação comercial no Brasil, e é justamente nesses sistemas que o CNPJ alfanumérico dá mais trabalho: campos Integer ou Int64, máscaras 99.999.999/9999-99 em TMaskEdit e funções de validação que fazem StrToInt em cada caractere. A unit abaixo troca essa conversão por Ord(c) - 48, que dá 0 a 9 para dígitos e 17 a 42 para letras de A a Z, e assim valida os dois formatos com o mesmo cálculo. Ela usa só a System.SysUtils e foi escrita para Delphi 10.4 ou mais recente. O guia CNPJ alfanumérico: o que muda no seu sistema lista os outros pontos a revisar.
Validar CNPJ em Delphi (numérico e alfanumérico)
unit CnpjUtils;
interface
function ValidarCNPJ(const S: string): Boolean;
function GerarCNPJ(Alfanumerico: Boolean = False; Formatado: Boolean = False): string;
function LimparCNPJ(const S: string): string;
function FormatarCNPJ(const S: string): string;
implementation
uses
System.SysUtils;
const
Pesos1: array[0..11] of Integer = (5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2);
Pesos2: array[0..12] of Integer = (6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2);
function CalcularDigito(const Base: string; const Pesos: array of Integer): Integer;
var
I, Soma, Resto: Integer;
begin
Soma := 0;
for I := 1 to Length(Base) do
Soma := Soma + (Ord(Base[I]) - 48) * Pesos[I - 1];
Resto := Soma mod 11;
if Resto < 2 then
Result := 0
else
Result := 11 - Resto;
end;
function ValidarCNPJ(const S: string): Boolean;
var
C: Char;
Cnpj, Base: string;
Dv1, Dv2: Integer;
begin
Result := False;
Cnpj := '';
for C in UpperCase(S) do
if CharInSet(C, ['0'..'9', 'A'..'Z']) then
Cnpj := Cnpj + C
else if not CharInSet(C, ['.', '/', '-', ' ']) then
Exit;
if Length(Cnpj) <> 14 then
Exit;
if not (CharInSet(Cnpj[13], ['0'..'9']) and CharInSet(Cnpj[14], ['0'..'9'])) then
Exit;
Base := Copy(Cnpj, 1, 12);
if Base = StringOfChar(Base[1], 12) then
Exit;
Dv1 := CalcularDigito(Base, Pesos1);
Dv2 := CalcularDigito(Base + IntToStr(Dv1), Pesos2);
Result := (Ord(Cnpj[13]) - 48 = Dv1) and (Ord(Cnpj[14]) - 48 = Dv2);
end;O que observar:
UpperCasedaSysUtilsconverte só de a até z. É o que se quer aqui:12.abc.345/01de-35vira12.ABC.345/01DE-35, e letras acentuadas continuam fora do conjunto aceito.CharInSeté a forma recomendada de testarCharcontra um conjunto desde questringpassou a ser Unicode;C in [...]gera aviso do compilador.- Só pontos, barra, hífen e espaços são ignorados. Qualquer outro símbolo faz a função sair com
False. - Strings começam no índice 1, por isso o laço vai de 1 a
Length(Base)e o peso é lido emPesos[I - 1], já que o array aberto começa em 0. StringOfChar(Base[1], 12)recusa bases repetidas como 00.000.000/0000-00, que fecham a conta, mas não são válidas.
Com 11.222.333/0001-81, a primeira soma é 102, o resto é 3 e o dígito é 8; a segunda soma é 120, o resto é 10 e o dígito é 1. Com 12.ABC.345/01DE-35, as somas são 459 e 424, os restos 8 e 6, e os dígitos 3 e 5.
Gerar CNPJ válido em Delphi
function GerarCNPJ(Alfanumerico: Boolean = False; Formatado: Boolean = False): string;
const
Alfabeto = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var
Base: string;
I, Tamanho, Dv1, Dv2: Integer;
begin
if Alfanumerico then
Tamanho := 36
else
Tamanho := 10;
repeat
Base := '';
for I := 1 to 12 do
Base := Base + Alfabeto[Random(Tamanho) + 1];
until Base <> StringOfChar(Base[1], 12);
Dv1 := CalcularDigito(Base, Pesos1);
Dv2 := CalcularDigito(Base + IntToStr(Dv1), Pesos2);
Result := Base + IntToStr(Dv1) + IntToStr(Dv2);
if Formatado then
Result := FormatarCNPJ(Result);
end;Random não é criptográfico, o que basta para dados de teste; chame Randomize uma vez na inicialização (o fim da unit, mais abaixo, já faz isso). O CNPJ gerado passa na validação, mas pode coincidir com um CNPJ real por acaso, então use-o só em bases de homologação. Para gerar filiais de uma mesma raiz, veja o guia da estrutura do CNPJ.
Formatar e limpar CNPJ em Delphi
function LimparCNPJ(const S: string): string;
var
C: Char;
begin
Result := '';
for C in UpperCase(S) do
if CharInSet(C, ['0'..'9', 'A'..'Z']) then
Result := Result + C;
end;
function FormatarCNPJ(const S: string): string;
var
C: string;
begin
C := LimparCNPJ(S);
if Length(C) <> 14 then
raise EArgumentException.Create('CNPJ precisa de 14 caracteres');
Result := Copy(C, 1, 2) + '.' + Copy(C, 3, 3) + '.' + Copy(C, 6, 3) + '/' +
Copy(C, 9, 4) + '-' + Copy(C, 13, 2);
end;
initialization
Randomize;
end.FormatarCNPJ('12abc34501de35') devolve '12.ABC.345/01DE-35', e aplicar a função de novo no resultado não muda nada. No banco, troque campos numéricos por CHAR(14) ou VARCHAR(14) e grave a versão limpa. No TMaskEdit, a máscara 99.999.999/9999-99;0;_ não aceita letras; use AA.AAA.AAA/AAAA-99;0;_ (em que A aceita letra ou dígito) e chame UpperCase no OnExit, ou trate a digitação em um TEdit comum com FormatarCNPJ.
Testes
Com DUnitX, cada atributo TestCase vira um teste separado. Os parâmetros chegam como texto separado por vírgula e são convertidos para os tipos do método.
unit CnpjUtilsTests;
interface
uses
DUnitX.TestFramework;
type
[TestFixture]
TCnpjTests = class
public
[Test]
[TestCase('NumericoValido', '11.222.333/0001-81,True')]
[TestCase('AlfanumericoValido', '12.ABC.345/01DE-35,True')]
[TestCase('DigitoErrado', '11.222.333/0001-82,False')]
[TestCase('Repetido', '00.000.000/0000-00,False')]
procedure Validar(const Cnpj: string; Esperado: Boolean);
[Test]
procedure GeradosSaoValidos;
[Test]
procedure FormatarEhIdempotente;
end;
implementation
uses
CnpjUtils;
procedure TCnpjTests.Validar(const Cnpj: string; Esperado: Boolean);
begin
Assert.IsTrue(ValidarCNPJ(Cnpj) = Esperado, Cnpj);
end;
procedure TCnpjTests.GeradosSaoValidos;
var
I: Integer;
begin
for I := 1 to 1000 do
begin
Assert.IsTrue(ValidarCNPJ(GerarCNPJ(False)));
Assert.IsTrue(ValidarCNPJ(GerarCNPJ(True)));
end;
end;
procedure TCnpjTests.FormatarEhIdempotente;
begin
Assert.AreEqual('12.ABC.345/01DE-35', FormatarCNPJ(FormatarCNPJ('12abc34501de35')));
end;
initialization
TDUnitX.RegisterTestFixture(TCnpjTests);
end.Se o projeto não usa DUnitX, os mesmos quatro casos cabem em um programa de console com Writeln(ValidarCNPJ('12.ABC.345/01DE-35')). Outros números estão nos exemplos de CNPJ alfanumérico para teste.
Usar a API em vez de reimplementar
A API do cpf.dev.br valida e gera CNPJs por HTTP, sem chave, com limite de 60 requisições por minuto por IP. Envie o CNPJ sem máscara, ou com a barra codificada como %2F. TNetHTTPClient e System.JSON já vêm com o Delphi. O trecho abaixo é só o corpo do procedimento que faz a chamada e lê a resposta; não é um programa completo — falta o uses do seu projeto e tratamento de erro de rede/timeout.
uses
System.SysUtils, System.JSON, System.Net.HttpClientComponent, CnpjUtils;
procedure ConsultarCNPJ(const Cnpj: string);
var
Http: TNetHTTPClient;
Json: TJSONValue;
Formatado: string;
begin
Http := TNetHTTPClient.Create(nil);
try
Json := TJSONObject.ParseJSONValue(Http.Get(
'https://www.cpf.dev.br/api/v1/cnpj/validar/' + LimparCNPJ(Cnpj)).ContentAsString(TEncoding.UTF8));
try
// {"valido":true,"cnpj":"12ABC34501DE35","formatado":"12.ABC.345/01DE-35",
// "formato":"alfanumerico","raiz":"12ABC345","ordem":"01DE","matriz":false}
if Json.TryGetValue<string>('formatado', Formatado) then
Writeln(Json.GetValue<Boolean>('valido'), ' ', Formatado)
else
Writeln('Resposta sem o campo "formatado" (ex.: CNPJ ausente na URL).');
finally
Json.Free;
end;
finally
Http.Free;
end;
end;Para gerar uma lista, chame https://www.cpf.dev.br/api/v1/cnpj/gerar?quantidade=5&formato=alfanumerico&formatado=true e leia o array cnpjs. Na validação de telas, a função local continua sendo a melhor opção, porque não depende de rede. Para conferir um número à mão, use o validador de CNPJ.