Skip to content

Desafio 07: NFS-e — Nota Fiscal de Serviços Eletrônica

🇧🇷 Integração com Nota Fiscal Eletrônica
🇬🇧 Electronic Invoice Integration


No Brasil, toda empresa de serviço precisa emitir NFS-e. Cada prefeitura tem seu próprio sistema, XML e SOAP. São 5.570 municípios, cada um com implementação diferente do padrão ABRASF. O desafio não é emitir uma nota — é emitir em qualquer município sem enlouquecer.

Switch: TypeScript vs Go

O que é NFS-e?

ConceitoDescrição
ABRASFPadrão nacional para NFS-e (XML, SOAP, WSDL)
Certificado A1/A3ICP-Brasil, não qualquer certificado
ISSImposto Sobre Serviços (varia por município)
RPSRecibo Provisão de Serviço (pré-nota)
NFS-eNota emitida após processamento pela prefeitura

Fluxo Completo

Desafios por Município

CidadeParticularidade
São PauloPadrão ABRASF 2.0, SSL mutual
Rio de JaneiroWS-Security obrigatório
Belo HorizonteXML com campos extras
CuritibaWSDL customizado
SalvadorCertificado A3 obrigatório

XML ABRASF

typescript
function buildNFSexml(data: NFSData): string {
  return `<?xml version="1.0" encoding="UTF-8"?>
<GerarNfseEnvio xmlns="http://www.abrasf.org.br/nfse">
  <Prestador>
    <Cnpj>${data.provider.cnpj}</Cnpj>
    <InscricaoMunicipal>${data.provider.municipalReg}</InscricaoMunicipal>
  </Prestador>
  <Servico>
    <Valores>
      <ValorServicos>${formatAmount(data.amount, data.cityCode)}</ValorServicos>
      <ValorIss>${calculateISS(data.amount, data.cityCode)}</ValorIss>
    </Valores>
    <ItemListaServico>${data.serviceCode}</ItemListaServico>
    <Discriminacao>${data.description}</Discriminacao>
    <CodigoMunicipio>${data.cityCode}</CodigoMunicipio>
  </Servico>
  <Tomador>
    <CpfCnpj>
      <Cnpj>${data.taker.cnpj}</Cnpj>
    </CpfCnpj>
    <RazaoSocial>${data.taker.name}</RazaoSocial>
  </Tomador>
</GerarNfseEnvio>`;
}

Assinatura Digital com ICP-Brasil

typescript
import { readFileSync } from 'fs';
import { createSign, createHash } from 'crypto';
import { Pkcs12 } from 'node-forge';

export class NFSeSigner {
  public signXml(xml: string, pfxPath: string, password: string): string {
    const pfxBuffer = readFileSync(pfxPath);
    const p12 = new Pkcs12(pfxBuffer, password);
    const privateKey = p12.getPrivateKey();
    const certificate = p12.getCertificate();

    const canonicalXml = this.canonicalize(xml);
    const digest = createHash('sha256').update(canonicalXml).digest('base64');

    const sign = createSign('RSA-SHA256').update(signedInfo).sign(privateKey);
    return xml.replace('</GerarNfseEnvio>', `${signature}</GerarNfseEnvio>`);
  }
}

Comparação: TypeScript vs Go

AspectoTypeScriptGo
XML buildingTemplate literalsencoding/xml
SOAP clientnode-fetchnet/http nativo
Crypto ICP-Brasilnode-forgecrypto/x509 nativo
Performance~1K notas/s~5K notas/s
Memory~100MB~20MB

Como testar

bash
# TypeScript
pnpm --filter @banking/nfse dev

# Go
cd packages/backend/nfse-go
go run .

# Emitir NFS-e
curl -X POST http://localhost:3008/nfse/emitir \
  -H "Content-Type: application/json" \
  -d '{"cnpj":"12345678000199","serviceCode":"1702","amount":1500.00,"cityCode":"3550308","taker":{"cnpj":"98765432000110","name":"Cliente LTDA"}}'

Lições aprendidas

  1. 5.570 municípios — Cada um com implementação diferente do ABRASF
  2. Certificado ICP-Brasil — Não é qualquer certificado
  3. XML é Sagrado — Um campo a mais = rejeição
  4. SOAP varia por cidade — WS-Security, mTLS, HTTPS simples
  5. ISS varia por município — Alíquota diferente em cada lugar
  6. Go simplifica crypto — Certificados ICP-Brasil na stdlib
  7. Template XML é perigoso — Prefira encoding/xml
  8. Teste com certificado de homologação — Nunca use A1 de produção
  9. Cache de WSDL — Prefeituras mudam WSDL sem aviso
  10. Log de todas as notas — Auditoria é obrigatória