Programação Por Equipe DevBras

Capa do artigo: API de nota fiscal emite, mas quem guarda o XML autorizado é você

API de nota fiscal emite, mas quem guarda o XML autorizado é você

Depois que a API de emissão autoriza a NF-e, baixe o XML com o protocolo, confira a chave, grave com hash em armazenamento seu e com cópia, e trate cancelamentos e cartas de correção do mesmo jeito.

Um cliente da distribuidora pede o XML de uma nota de oito meses atrás para a contabilidade dele. A equipe abre o sistema e encontra só o número da nota e um status "autorizada". O XML ficou no painel da API de emissão, que a empresa trocou por outro provedor há três meses. A API emite a nota por você; a obrigação de guardar o XML continua sua.

A resposta curta: assim que a nota for autorizada, baixe o XML completo (a nota com o protocolo de autorização), confira que ele corresponde à chave esperada, grave em armazenamento sob seu controle com o hash registrado no banco e mantenha uma segunda cópia. Faça o mesmo com os XMLs de eventos, como cancelamento e carta de correção. O painel do provedor é conveniência, não arquivo.

Por que o sucesso da API não basta

Serviços de emissão de NF-e, como o do repositório de exemplo em C# que originou este post, cuidam de montar, assinar e transmitir a nota à SEFAZ. A resposta de sucesso traz a chave de acesso, o status e, em geral, um jeito de obter o XML e o DANFE. É tentador gravar só a chave e o status e seguir em frente, porque "está tudo lá no provedor".

O problema é que o provedor é um fornecedor. O contrato pode acabar, a conta pode ser encerrada, o serviço pode ter prazo próprio de retenção ou simplesmente sair do ar. A legislação fiscal exige que o emitente guarde os XMLs pelo prazo legal e os apresente quando solicitado; confirme com o seu contador qual é esse prazo e em que formato guardar. Se o único lugar onde o arquivo existe é o painel de alguém, você não tem o arquivo. Isto não substitui a orientação de um contador; serve para o sistema não depender da memória de terceiros.

O post sobre biblioteca de nota fiscal não substituir o contador trata da parte de obrigação. Aqui o foco é técnico: o que gravar, quando e como provar que o arquivo guardado é o certo.

O que precisa ser guardado

  • O XML de distribuição da nota, com a raiz nfeProc, que reúne a nota assinada (NFe) e o protocolo de autorização (protNFe). A nota sem o protocolo não prova que foi autorizada.
  • Os XMLs de eventos: cancelamento, carta de correção e outros que o seu fluxo usar, também com o retorno de registro.
  • Os metadados no banco: chave de acesso, número do protocolo, data de autorização, hash do arquivo e onde ele está.

O DANFE em PDF pode ser regerado a partir do XML; o XML não pode ser regerado a partir do PDF. A prioridade é clara.

Como guardar o XML autorizado, passo a passo

  1. Ao receber a autorização, baixe o XML completo pelo meio que o provedor oferece. Faça isso no mesmo fluxo, ou numa fila com nova tentativa, nunca "depois, se lembrar".
  2. Confira o conteúdo: o atributo Id do infNFe deve ser "NFe" seguido da chave esperada, e o protocolo deve indicar uso autorizado.
  3. Grave o arquivo num caminho previsível, organizado pelo ano e mês que já estão na chave.
  4. Registre no banco a chave, o protocolo, o hash SHA-256 e o caminho.
  5. Replique para um segundo local, de preferência com versionamento ou retenção que impeça apagar por engano.
  6. Repita para cada evento da nota.
  7. Teste a recuperação: periodicamente, sorteie notas, leia o arquivo e confira o hash.

Exemplo em C#

O código abaixo recebe a chave e o endereço do XML devolvido pelo provedor. O nome exato do campo na resposta depende do serviço; consulte a documentação dele. A partir do XML, tudo é padrão da NF-e: namespace, elementos e o código de status.

using System.Security.Cryptography;
using System.Xml.Linq;

public sealed class ArquivoFiscal
{
    private static readonly XNamespace Nfe = "http://www.portalfiscal.inf.br/nfe";
    private static readonly HashSet<string> Autorizada = new() { "100", "150" };

    private readonly HttpClient _http;
    private readonly AppDbContext _db;
    private readonly string _raiz;

    public ArquivoFiscal(HttpClient http, AppDbContext db, string raiz)
    {
        _http = http;
        _db = db;
        _raiz = raiz;
    }

    public async Task GuardarAsync(string chave, Uri urlXml, CancellationToken ct)
    {
        byte[] conteudo = await _http.GetByteArrayAsync(urlXml, ct);

        XDocument doc;
        using (var stream = new MemoryStream(conteudo))
        {
            doc = await XDocument.LoadAsync(stream, LoadOptions.None, ct);
        }

        string? id = doc.Descendants(Nfe + "infNFe").FirstOrDefault()?.Attribute("Id")?.Value;
        XElement? prot = doc.Descendants(Nfe + "infProt").FirstOrDefault();
        string? cStat = prot?.Element(Nfe + "cStat")?.Value;
        string? protocolo = prot?.Element(Nfe + "nProt")?.Value;

        if (id != "NFe" + chave)
        {
            throw new InvalidOperationException("XML recebido não corresponde à chave " + chave);
        }
        if (cStat is null || !Autorizada.Contains(cStat) || protocolo is null)
        {
            throw new InvalidOperationException("XML sem protocolo de autorização para " + chave);
        }

        string hash = Convert.ToHexString(SHA256.HashData(conteudo));
        string anoMes = chave.Substring(2, 4);
        string pasta = Path.Combine(_raiz, anoMes);
        Directory.CreateDirectory(pasta);

        string destino = Path.Combine(pasta, chave + "-procNFe.xml");
        string temporario = destino + ".tmp";
        await File.WriteAllBytesAsync(temporario, conteudo, ct);
        File.Move(temporario, destino, overwrite: true);

        _db.XmlsFiscais.Add(new XmlFiscal
        {
            Chave = chave,
            Protocolo = protocolo,
            Sha256 = hash,
            Caminho = destino,
            GuardadoEm = DateTimeOffset.UtcNow
        });
        await _db.SaveChangesAsync(ct);
    }
}

Alguns pontos do exemplo. A chave de acesso tem 44 dígitos, e as posições 3 a 6 trazem ano e mês de emissão, o que dá a organização das pastas. O código 100 indica uso autorizado; o 150 indica autorização fora de prazo, que também é nota válida. A gravação vai primeiro para um arquivo temporário e depois é movida, para que uma falha no meio não deixe um XML pela metade com o nome final. Se qualquer conferência falhar, a exceção deve levar a nota para uma fila de pendências visível, não sumir num log.

Em produção, troque o disco local por um armazenamento de objetos com versionamento e uma política de retenção, e mantenha a segunda cópia em outra conta ou região. O raciocínio é o mesmo de um backup de banco: só vale o que você já testou restaurar, como explica o post sobre backup que restaura de verdade.

Credenciais e ambiente

O certificado digital A1 e o token da API são segredos. Não ficam no repositório, nem no projeto de exemplo que você clonou para testar; vão para o gerenciador de segredos ou variáveis de ambiente do servidor. Não imprima o token nem a resposta completa no log de sucesso, como alerta o post sobre log sem senha. Se a chave vazar, troque no provedor antes de qualquer outra coisa. E feche todo o fluxo, inclusive a guarda do XML, no ambiente de homologação antes de emitir a primeira nota real.

Erros comuns

  • Guardar só a chave e o status: quando o provedor sai do ar ou o contrato acaba, não há XML para entregar ao cliente nem ao fisco.
  • Guardar a nota sem o protocolo: o arquivo não prova que a nota foi autorizada.
  • Baixar o XML "depois", fora do fluxo: falhas silenciosas deixam buracos que só aparecem meses depois.
  • Esquecer os eventos: o cancelamento existe na SEFAZ, mas o seu arquivo mostra a nota como válida.
  • Uma cópia só, no mesmo servidor: o disco que falha leva o histórico fiscal junto.
  • Certificado e token no repositório: quem tem acesso ao código pode emitir nota em nome da empresa.

Checklist

  • Toda nota autorizada tem o XML com protNFe gravado em armazenamento seu.
  • O sistema confere chave e código de status antes de gravar.
  • Banco registra chave, protocolo, hash e caminho de cada arquivo.
  • Existe uma segunda cópia com retenção ou versionamento.
  • Eventos de cancelamento e correção são guardados do mesmo jeito.
  • Falhas de download caem numa fila de pendências visível.
  • Certificado e token estão fora do repositório e fora dos logs.
  • Uma verificação periódica sorteia notas e confere o hash dos arquivos.

Faça o exercício agora: se a conta no provedor de emissão fosse encerrada hoje, você conseguiria entregar o XML da primeira nota que o sistema emitiu?

Avalie este artigo

Referência: https://github.com/webmaniabr/NFe-C-Sharp