NF-e rejeitada com XML que parece perfeito: schema, assinatura e o editor que reformata
Valide o XML da nota contra o XSD da versão declarada, assine sem tocar depois, confira a assinatura localmente e só considere pronta a nota com protocolo.
Quinta, 17h40, a nota 1532 da Ferragens Lima volta rejeitada. O desenvolvedor abre o XML assinado no editor, o arquivo está lindo, indentado, cada tag no lugar. Ele aperta "formatar documento" para ler melhor, transmite de novo e a rejeição muda: agora é a assinatura que não confere. O XML ficou mais bonito e menos válido.
A resposta curta: quem decide se a nota é válida é o schema da versão que você declarou e a assinatura calculada sobre os bytes exatos do XML, não a aparência no editor. Valide contra o XSD antes de transmitir, assine e não toque mais no arquivo, confira a assinatura localmente e só chame de pronta a nota que voltou com protocolo de autorização, primeiro em homologação.
Por que o XML que parece certo é rejeitado
A nota fiscal eletrônica passa por três etapas diferentes: gerar o XML, assinar com o certificado digital e transmitir para o webservice da Secretaria da Fazenda. Cada etapa tem um jeito próprio de falhar, e nenhum deles aparece a olho nu.
O schema confere estrutura, não aparência
O leiaute da NF-e é publicado como um pacote de arquivos XSD. Eles definem a ordem dos elementos, quais são obrigatórios, o tamanho de cada campo, os padrões aceitos e os valores possíveis. Um CEP escrito como 01310-100 em vez de oito dígitos, um valor com vírgula em vez de ponto decimal ou um elemento fora da sequência esperada derrubam a nota. No editor, tudo isso parece normal.
O schema que vale é o da versão declarada no próprio XML, no atributo versao do grupo infNFe. Se você declara uma versão e monta o conteúdo seguindo um exemplo antigo de outra, a rejeição vem mesmo que "o XML esteja certo". Por isso dica de fórum com dez anos de idade é perigosa, como mostra o post sobre dica de nota fiscal que tem data.
A assinatura confere bytes, não significado
A assinatura digital da NF-e segue o padrão XML Signature: o conteúdo do grupo infNFe é canonicalizado, vira um resumo criptográfico e esse resumo é assinado com a chave do certificado. A referência da assinatura aponta para o atributo Id desse grupo, que começa com "NFe" seguido da chave de acesso.
A canonicalização padroniza detalhes como a ordem dos atributos, mas não remove espaços e quebras de linha entre as tags. Esses espaços são nós de texto e entram no cálculo. Indentar o arquivo depois de assinar, trocar o fim de linha, corrigir "só um acento" ou regravar com outro encoding muda o resumo. O fisco recalcula, compara e rejeita.
Como gerar, validar, assinar e transmitir sem surpresa
O fluxo abaixo usa a nota 1532 da Ferragens Lima como exemplo. Os passos valem para qualquer linguagem; os exemplos usam xmllint e C#.
- Gere o XML com serialização, não com concatenação de texto. Objetos ou um gerador de XML cuidam de escape e de ordem. O post sobre montar o XML da nota sem concatenar string detalha os riscos.
- Baixe o pacote de schemas da versão que você declara, no Portal da NF-e, e guarde junto do código, com o nome da versão.
- Assine o grupo
infNFecom o certificado, carregado de um cofre de segredos ou variável de ambiente, nunca com a senha escrita no código. - Valide o arquivo assinado contra o XSD. O schema da nota inclui o bloco da assinatura, então a validação faz mais sentido logo antes de transmitir.
- Confira a assinatura localmente, lendo o arquivo exatamente como ele será enviado.
- Transmita em homologação, com o ambiente marcado como homologação no XML, e leia os campos
cStatexMotivodo retorno. - Guarde o XML autorizado com o protocolo, o retorno bruto e a versão do schema usada.
Validar contra o XSD antes de transmitir
Com o pacote de schemas extraído numa pasta, o xmllint, que vem com a libxml2, valida o arquivo em segundos. O nome exato do XSD principal depende do pacote que você baixou; confira na pasta.
xmllint --noout --schema schemas/nfe_v4.00.xsd nota-1532-assinada.xml
Quando passa, a saída diz que o arquivo valida. Quando falha, ela aponta o elemento, a regra e o valor. Na Ferragens Lima, o primeiro erro foi exatamente o CEP com hífen: a mensagem citava o elemento CEP, a faceta de padrão e o valor recusado. Corrigido no gerador, não no arquivo, o problema não voltou nas notas seguintes.
Coloque essa validação no pipeline de testes com algumas notas de exemplo de cada tipo de operação. Uma mudança no gerador que quebra o schema aparece no pull request, não na rejeição de sexta à noite.
Conferir a assinatura do jeito que ela será enviada
Em .NET, a mesma API que assina também confere. O detalhe que importa é PreserveWhitespace = true: sem ele, o carregamento descarta espaços e você confere um documento diferente do que vai transmitir.
using System;
using System.Security.Cryptography.Xml;
using System.Xml;
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load("nota-1532-assinada.xml");
var no = (XmlElement)doc.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0];
var assinatura = new SignedXml(doc);
assinatura.LoadXml(no);
Console.WriteLine(assinatura.CheckSignature() ? "assinatura confere" : "assinatura NÃO confere");
Use a mesma regra ao gravar: depois de assinar, salve o documento sem formatação extra e sem passar por nenhuma etapa que "limpe" o XML. Se alguém precisa ler a nota indentada, gere uma cópia para leitura com outro nome e nunca a transmita.
Ler o retorno e guardar a prova
O webservice responde com um código de status em cStat e uma descrição em xMotivo. Grave os dois, junto com o XML enviado, antes de qualquer outra ação. Quando a nota é autorizada, o retorno traz o protocolo; o arquivo que vale como documento é o XML da nota unido ao protocolo de autorização, normalmente num envelope nfeProc. Esse é o arquivo que você guarda e entrega ao cliente.
Homologação existe para errar sem valor fiscal. Teste nela cada tipo de operação que o sistema emite, inclusive cancelamento e rejeições esperadas. O post sobre homologação que não é produção com outro endereço explica o que muda entre os ambientes.
Erros comuns
- Formatar o XML depois de assinar: a indentação muda os nós de texto, o resumo recalculado não bate e a nota é rejeitada por assinatura.
- Validar só "no olho" ou só no retorno do fisco: cada erro de schema custa uma ida e volta, e em produção cada ida e volta é uma venda parada.
- Seguir exemplo de outra versão do leiaute: o XML parece certo, mas o schema que confere é o da versão declarada.
- Senha do certificado A1 no código ou no repositório: quem tem o arquivo e a senha assina documentos fiscais em nome da empresa.
- Copiar uma nota de produção para "ver como fica" em homologação, ou o contrário: ambiente errado gera rejeição no melhor caso e documento com valor fiscal indesejado no pior.
- Não guardar o retorno bruto: sem
cStat,xMotivoe protocolo, você não prova o que foi autorizado nem diagnostica o que falhou.
Checklist
- O XML é gerado por serialização, não por concatenação de texto.
- O pacote de schemas da versão declarada está versionado junto do código.
- O arquivo assinado passa no xmllint antes de toda transmissão.
- Nenhuma etapa depois da assinatura reformata, reindenta ou recodifica o XML.
- A assinatura é conferida localmente com espaços preservados.
- Certificado e senha vêm de cofre ou variável de ambiente, fora do código.
- Cada tipo de operação foi autorizado em homologação, com protocolo.
- XML autorizado, protocolo e retorno bruto ficam guardados juntos.
Isto descreve a parte técnica. Qual documento emitir, com quais dados fiscais, é decisão de quem responde pela contabilidade da empresa, e este texto não substitui um contador.
Pegue a última nota que o seu sistema chamou de pronta: existe um protocolo de autorização guardado ao lado dela, ou só um XML bonito numa pasta?
Referência: http://www.linhadecodigo.com.br/artigo/1814/nota-fiscal-eletronica-geracao-assinatura-e-transmissao.aspx