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?
| Conceito | Descrição |
|---|---|
| ABRASF | Padrão nacional para NFS-e (XML, SOAP, WSDL) |
| Certificado A1/A3 | ICP-Brasil, não qualquer certificado |
| ISS | Imposto Sobre Serviços (varia por município) |
| RPS | Recibo Provisão de Serviço (pré-nota) |
| NFS-e | Nota emitida após processamento pela prefeitura |
Fluxo Completo
Desafios por Município
| Cidade | Particularidade |
|---|---|
| São Paulo | Padrão ABRASF 2.0, SSL mutual |
| Rio de Janeiro | WS-Security obrigatório |
| Belo Horizonte | XML com campos extras |
| Curitiba | WSDL customizado |
| Salvador | Certificado 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
| Aspecto | TypeScript | Go |
|---|---|---|
| XML building | Template literals | encoding/xml |
| SOAP client | node-fetch | net/http nativo |
| Crypto ICP-Brasil | node-forge | crypto/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
- 5.570 municípios — Cada um com implementação diferente do ABRASF
- Certificado ICP-Brasil — Não é qualquer certificado
- XML é Sagrado — Um campo a mais = rejeição
- SOAP varia por cidade — WS-Security, mTLS, HTTPS simples
- ISS varia por município — Alíquota diferente em cada lugar
- Go simplifica crypto — Certificados ICP-Brasil na stdlib
- Template XML é perigoso — Prefira encoding/xml
- Teste com certificado de homologação — Nunca use A1 de produção
- Cache de WSDL — Prefeituras mudam WSDL sem aviso
- Log de todas as notas — Auditoria é obrigatória