Enviar o XML da NF-e ao cliente sem mandar a nota de outro pedido
Automatize o envio do XML da nota: só depois da autorização, com o arquivo nomeado pela chave, conferido contra o pedido e com uma fila de envio que repete o e-mail sem gerar outra nota.
Segunda-feira, 10h12, a loja de ferragens emitiu duas notas no mesmo segundo. As duas rotinas gravaram o arquivo como temp/nota.xml, e o cliente do pedido 10432 recebeu o XML do vizinho de fila, com nome, CPF e endereço de outra pessoa. A resposta curta: o XML que segue para o cliente é o arquivo autorizado daquela venda, identificado pela chave de acesso e conferido contra o pedido antes de sair. Dispare só depois da autorização, nomeie pelo número da chave, confira chave e destinatário, e use uma fila de envio que repete o e-mail sem nunca emitir outra nota.
Este guia monta esse fluxo com o pedido 10432 como exemplo, em PHP e MySQL, mas a lógica vale para qualquer linguagem.
Por que o XML errado sai
O envio automático costuma nascer como três linhas no fim da emissão: gravar o arquivo, montar o e-mail, anexar. Isso esconde três suposições frágeis. A primeira é que o arquivo no disco é o da venda atual; com nome fixo e duas emissões simultâneas, não é. A segunda é que a emissão terminou bem; se a rotina envia logo depois de transmitir, pode anexar um XML ainda sem protocolo de autorização, ou de uma nota rejeitada. A terceira é que o e-mail funciona na primeira tentativa; quando ele falha, alguém aperta "emitir de novo" para "reenviar", e aí nasce uma segunda nota para a mesma venda.
O XML da NF-e não é um anexo qualquer. Ele traz dados pessoais do destinatário, tem valor fiscal e é identificado por uma chave de acesso de 44 dígitos. Tratar o envio como "mandar um arquivo" é o que permite os três erros.
Qual arquivo enviar
O arquivo que o cliente precisa é o XML da nota junto com o protocolo de autorização, normalmente chamado de XML de distribuição: a raiz nfeProc contém a NFe assinada e o protNFe com a resposta da Sefaz. Dentro do protocolo, cStat igual a 100 indica uso autorizado e chNFe traz a chave. O XML que você montou e assinou, antes de transmitir, ainda não é esse arquivo. A regra da NF-e prevê que o emitente disponibilize o XML ao destinatário; a forma de fazer isso na sua operação é assunto para confirmar com o contador, porque isto não substitui a orientação de um profissional de contabilidade.
Como montar o envio automático
- Depois da autorização, grave o XML de distribuição com o nome da chave, por exemplo
35261012345678000199550010000104321000104328-procNFe.xml, numa pasta definitiva, nunca num arquivo temporário com nome fixo. - Grave no pedido a chave e o status conferido no protocolo.
- Crie uma linha na fila de envio com pedido, chave e destinatário.
- Um processo separado lê a fila, reabre o arquivo pela chave e confere chave, status e documento do destinatário contra o pedido.
- Só então envia o e-mail. Se falhar, registra o erro e tenta de novo depois, com o mesmo arquivo.
- Se a nota for cancelada mais tarde, envie também o XML do evento de cancelamento.
A fila de envio
CREATE TABLE envio_xml (
id INT AUTO_INCREMENT PRIMARY KEY,
pedido_id INT NOT NULL,
chave CHAR(44) NOT NULL,
destinatario VARCHAR(200) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pendente',
tentativas INT NOT NULL DEFAULT 0,
ultimo_erro VARCHAR(500) NULL,
enviado_em DATETIME NULL,
UNIQUE KEY uq_envio (chave, destinatario)
);
A chave única em (chave, destinatario) impede duas linhas para o mesmo envio. Reenviar é mudar o status de volta para pendente, não emitir nada.
A conferência antes de anexar
<?php
function conferirXmlParaEnvio(string $caminho, array $pedido): void
{
$doc = new DOMDocument();
if (!$doc->load($caminho)) {
throw new RuntimeException('XML ilegível: ' . $caminho);
}
$xp = new DOMXPath($doc);
$xp->registerNamespace('n', 'http://www.portalfiscal.inf.br/nfe');
$chave = $xp->evaluate('string(/n:nfeProc/n:protNFe/n:infProt/n:chNFe)');
$status = $xp->evaluate('string(/n:nfeProc/n:protNFe/n:infProt/n:cStat)');
$docDest = $xp->evaluate('string(/n:nfeProc/n:NFe/n:infNFe/n:dest/n:CNPJ)')
?: $xp->evaluate('string(/n:nfeProc/n:NFe/n:infNFe/n:dest/n:CPF)');
if ($status !== '100') {
throw new RuntimeException("Nota sem autorização (cStat $status).");
}
if ($chave !== $pedido['nfe_chave']) {
throw new RuntimeException("Chave do arquivo ($chave) diferente da do pedido.");
}
$docPedido = preg_replace('/[^0-9]/', '', $pedido['cliente_documento']);
if ($docDest !== $docPedido) {
throw new RuntimeException('Destinatário do XML não é o cliente do pedido.');
}
}
Se qualquer conferência falhar, o envio fica com status erro e a mensagem vai para ultimo_erro, para alguém olhar. No caso da segunda-feira, a função teria parado o envio: a chave do arquivo nota.xml não era a do pedido 10432. O nome do arquivo pela chave, por si só, já teria evitado a troca; a conferência pega o que o nome não pega, como um pedido ligado à nota errada por um bug de tela.
O e-mail em si
O endereço vem do cadastro do pedido, que é o que o cliente informou na compra. O assunto leva o número da nota e o nome da loja; o corpo diz o número do pedido e a chave, para o cliente conferir no portal da NF-e se quiser. Anexe o XML e, se você gera, o PDF do DANFE. Use um serviço de envio com SPF e DKIM configurados, senão o e-mail da nota cai no spam; o guia de formulário de contato que chega explica essa parte.
Guardar o arquivo é obrigação sua
Mandar o XML ao cliente não dispensa você de guardar o seu. O arquivo de distribuição precisa ficar armazenado pelo prazo que a legislação exige, mesmo que a emissão seja feita por uma API de terceiros. O post sobre API de nota que não guarda por você trata disso. Para entender o que existe dentro do XML antes da autorização, veja o XML válido e o que a tela mostrou.
Erros comuns
- Arquivo temporário com nome fixo. Duas emissões simultâneas sobrescrevem o mesmo arquivo e um cliente recebe os dados de outro.
- Enviar logo depois de transmitir. Vai um XML sem protocolo, ou de uma nota rejeitada, que não serve ao cliente.
- Emitir de novo para reenviar o e-mail. Nasce uma segunda nota para a mesma venda, que depois precisa ser cancelada.
- Não conferir o destinatário. Um pedido ligado à nota errada manda dados pessoais para a pessoa errada.
- Esquecer o evento de cancelamento. O cliente fica com uma nota que, para a Sefaz, não vale mais.
- Enviar sem registrar. Quando o cliente diz que não recebeu, ninguém sabe se o e-mail saiu, falhou ou nem foi tentado.
Checklist
- O XML enviado é o de distribuição, com protocolo e
cStat100. - O arquivo é nomeado e encontrado pela chave de acesso.
- Antes de anexar, chave e documento do destinatário são conferidos contra o pedido.
- O envio passa por uma fila com status, tentativas e último erro.
- Reenviar não emite outra nota.
- Cancelamentos também geram envio do XML do evento.
- O e-mail sai por um serviço com SPF e DKIM configurados.
Faça agora um teste de cinco minutos: emita uma nota em homologação, abra o e-mail que chegou e compare a chave do anexo com a chave na tela do pedido. São a mesma? E se duas notas forem emitidas no mesmo segundo, o seu sistema ainda manda cada uma para o dono certo?