Como parsear NF-e e NFC-e em JavaScript (e por que é mais complexo do que parece)
Se você já tentou extrair dados de uma nota fiscal eletrônica brasileira
programaticamente, sabe que não é trivial. Neste artigo explico como
funciona o XML da NF-e, como fazer o parse e os problemas que você
vai encontrar no caminho.
O que é a NF-e / NFC-e
A Nota Fiscal Eletrônica (NF-e) e a Nota Fiscal do Consumidor Eletrônica
(NFC-e) são documentos fiscais emitidos em XML, definidos pela SEFAZ
federal. Cada cupom fiscal com QR Code contém uma URL que aponta para
o portal SEFAZ do estado emissor.
O QR Code de um cupom de supermercado em Pernambuco abre algo assim:
https://nfce.sefaz.pe.gov.br/nfce/consulta?p=26260308845439...
Ao acessar essa URL, o portal retorna um XML padronizado — o mesmo
schema para todos os 27 estados.
A estrutura do XML
O XML segue o padrão nacional NFe 4.0. As tags mais importantes:
<nfeProc>
<NFe>
<infNFe>
<emit>
<xNome>SUPERMERCADO EXEMPLO LTDA</xNome>
<xFant>SuperExemplo</xFant>
<CNPJ>12345678000199</CNPJ>
</emit>
<det nItem="1">
<prod>
<xProd>ARROZ BRANCO TIO JOAO 5KG</xProd>
<cEAN>7896006716113</cEAN>
<qCom>2.000</qCom>
<uCom>KG</uCom>
<vUnCom>4.9800</vUnCom>
<vProd>9.96</vProd>
<vDesc>0.00</vDesc>
</prod>
</det>
<total>
<ICMSTot>
<vNF>187.43</vNF>
</ICMSTot>
</total>
</infNFe>
</NFe>
</nfeProc>
Parser sem dependências externas
Dá para extrair os dados com regex puro — sem XML parser, sem
dependências. O truque é capturar conteúdo entre tags com o flag s
(dotAll) do ES2018:
function extractTag(xml, tag) {
const m = xml.match(new RegExp(`<${tag}(?:\\s[^>]*)?>(.*?)</${tag}>`, 's'))
return m ? m[1].trim() : null
}
function parseItems(xml) {
const items = []
const detRegex = /<det\s+nItem="(\d+)">(.*?)<\/det>/gs
let m
while ((m = detRegex.exec(xml)) !== null) {
const [, nItem, det] = m
const prod = extractTag(det, 'prod') ?? ''
const gross = parseFloat(extractTag(prod, 'vProd') ?? '0')
const discount = parseFloat(extractTag(prod, 'vDesc') ?? '0')
items.push({
n: parseInt(nItem),
name: extractTag(prod, 'xProd'),
ean: extractTag(prod, 'cEAN'),
qty: parseFloat(extractTag(prod, 'qCom') ?? '1'),
unit: extractTag(prod, 'uCom'),
unitPrice: parseFloat(extractTag(prod, 'vUnCom') ?? '0'),
total: Math.max(0, gross - discount),
discount,
})
}
return items
}
Os problemas que você vai encontrar
1. Cada estado tem um portal diferente
A URL varia por estado. PE usa nfce.sefaz.pe.gov.br, SP usa
www.nfce.fazenda.sp.gov.br, etc. A chave de acesso (44 dígitos no
QR code) tem os dois primeiros dígitos como código UF — dá para
identificar o estado e construir a URL correta.
2. Portais lentos ou instáveis
A SEFAZ de alguns estados pode levar 5-10 segundos para responder,
ou estar fora em horários de pico. Timeout generoso (20s+) é necessário.
3. Headers HTTP específicos
Alguns portais retornam erro 403 sem um User-Agent realista:
const res = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
'Accept': 'text/html,application/xhtml+xml,application/xml',
'Accept-Language': 'pt-BR,pt;q=0.9',
}
})
4. XML vs HTML
Em horários de pico, alguns portais retornam uma página HTML de erro
em vez do XML. Vale verificar se o retorno começa com <?xml antes
de parsear.
Se não quiser manter isso você mesmo
Construí a NFParse API para resolver exatamente isso: você manda a URL
da NF-e, recebe JSON estruturado em segundos. Funciona com todos os
27 estados, tem plano gratuito de 100 req/mês e não usa Puppeteer nem
Playwright — parse direto do XML.
→ nfparse.com.br
Dúvidas sobre o parsing ou sobre o padrão XML da NF-e? Comenta aí.
Fonte: https://nfparse.com.br