Multi-Tenant - 10 - Exemplo de NFSe para o Ambiente Nacional em Golang
Beleza, vamos amarrar tudo: XML de envio “estilo nacional/ABRASF” + fluxo completo API → map → persistir → ACBr → retorno → atualizar nfse.
Vou dividir em duas partes:
- Esboço de XML (a partir do
NfseNacionalPayloadque montamos). - Fluxo completo em passos + diagrama.
Observação: o XML abaixo é ilustrativo, inspirado no padrão ABRASF / Nacional, não é cópia literal do schema oficial. A ideia é servir de “ponte mental” entre o DTO e o que o ACBr / Ambiente Nacional esperam.
1. Esboço do XML de envio
Supondo que você vai enviar um RPS dentro de um lote para o Ambiente Nacional via ACBr (algo como “Enviar Lote RPS”):
<EnviarLoteRpsEnvio xmlns="http://www.abrasf.org.br/nfse.xsd">
<LoteRps Id="Lote_ABC-2025-0001" versao="2.04">
<NumeroLote>1</NumeroLote>
<Cnpj>12345678000199</Cnpj>
<InscricaoMunicipal>123456</InscricaoMunicipal>
<QuantidadeRps>1</QuantidadeRps>
<ListaRps>
<Rps>
<InfRps Id="RPS_12345">
<!-- Identificação do RPS -->
<IdentificacaoRps>
<Numero>12345</Numero>
<Serie>UNICA</Serie>
<Tipo>1</Tipo>
</IdentificacaoRps>
<!-- Data de emissão e natureza da operação -->
<DataEmissao>2025-11-10T10:30:00</DataEmissao>
<NaturezaOperacao>1</NaturezaOperacao>
<RegimeEspecialTributacao>6</RegimeEspecialTributacao>
<OptanteSimplesNacional>1</OptanteSimplesNacional>
<IncentivadorCultural>2</IncentivadorCultural> <!-- 1=sim, 2=não -->
<!-- Serviço -->
<Servico>
<Valores>
<ValorServicos>1000.00</ValorServicos>
<ValorDeducoes>0.00</ValorDeducoes>
<ValorPis>0.00</ValorPis>
<ValorCofins>0.00</ValorCofins>
<ValorInss>0.00</ValorInss>
<ValorIr>0.00</ValorIr>
<ValorCsll>0.00</ValorCsll>
<OutrasRetencoes>0.00</OutrasRetencoes>
<DescontoIncondicionado>0.00</DescontoIncondicionado>
<DescontoCondicionado>0.00</DescontoCondicionado>
<ValorIss>20.00</ValorIss>
<IssRetido>2</IssRetido> <!-- 1=sim,2=não -->
</Valores>
<ItemListaServico>14.01</ItemListaServico>
<CodigoTributacaoMunicipio>0107</CodigoTributacaoMunicipio>
<Discriminacao>Serviços prestados conforme contrato nº 123</Discriminacao>
<CodigoMunicipio>3106200</CodigoMunicipio>
</Servico>
<!-- Prestador -->
<PrestadorServico>
<IdentificacaoPrestador>
<Cnpj>12345678000199</Cnpj>
<InscricaoMunicipal>123456</InscricaoMunicipal>
</IdentificacaoPrestador>
</PrestadorServico>
<!-- Tomador -->
<TomadorServico>
<IdentificacaoTomador>
<CpfCnpj>
<Cnpj>98765432000177</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>000000</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>Cliente de Exemplo LTDA</RazaoSocial>
<Endereco>
<Endereco>Rua X</Endereco>
<Numero>100</Numero>
<Complemento>Sala 3</Complemento>
<Bairro>Centro</Bairro>
<CodigoMunicipio>3106200</CodigoMunicipio>
<Uf>MG</Uf>
<Cep>30140000</Cep>
<CodigoPais>1058</CodigoPais>
</Endereco>
<Contato>
<Telefone>31999999999</Telefone>
<Email>financeiro@cliente.com</Email>
</Contato>
</TomadorServico>
<!-- Informações adicionais -->
<OutrasInformacoes>Observação extra que vai no campo livre</OutrasInformacoes>
</InfRps>
</Rps>
</ListaRps>
</LoteRps>
</EnviarLoteRpsEnvio>
Como isso casa com o seu NfseNacionalPayload:
-
IdentificacaoRps→<IdentificacaoRps> -
PrestadorServico→<PrestadorServico> -
TomadorServico→<TomadorServico> -
Servico→<Servico>(com<Valores>,<ItemListaServico>,<CodigoTributacaoMunicipio>,<Discriminacao>, etc.) -
RegimeTributario→ campos de<NaturezaOperacao>,<RegimeEspecialTributacao>,<OptanteSimplesNacional>,<IncentivadorCultural> -
InformacoesAdicionais→<OutrasInformacoes>ou campos equivalentes -
Transmissao(LoteIDExterno,DataEmissao,Ambiente) → atributos/elementos do<LoteRps>ou metadados do envio.
Na prática, no Delphi/ACBr você vai popular as propriedades do componente (ACBrNFSe.NotasFiscais.Add.NFSe.Servico.Valores.ValorServicos, etc.), e ele gera esse XML conforme o provedor.
2. Fluxo completo: API → map → persistir → ACBr → retorno → atualizar nfse
Vou considerar um cenário síncrono “clássico” (tudo na mesma requisição), e em seguida dou o pitaco de como fazer assíncrono (que é o que eu recomendo pro mundo real).
2.1. Passo a passo (visão síncrona)
-
Cliente (frontend / Delphi / outro sistema) → sua API Go
- Envia
POST /tenants/{tenantID}/nfsecom JSON no formatoEmissaoNfseRequest.
- Envia
-
Handler HTTP (Go)
-
Recupera
tenant_id(da rota ou do contexto). -
json.NewDecoder(r.Body).Decode(&req)→EmissaoNfseRequest. -
Chama
MapEmissaoToDomain(tenantID, req):-
Gera
Nfse(cabeçalho interno). -
Gera
[]NfseItem(itens internos).
-
-
-
Persiste no banco (Go + Postgres)
-
Middleware de Tx abre transação e faz:
SET LOCAL app.tenant_id = :tenant_id; -
service.CriarNfseCompleta(ctx, tenantID, nf, itens):-
repo.CriarNfse(ctx, &nf, itens) -
Insere na tabela
nfse. -
Insere na tabela
nfse_itens. -
nf.idretornado.
-
-
Situação inicial da NFS-e → algo como
situacao = 'pendente_transmissao'ousituacao = 'em_processamento'.
-
-
Mapeia para payload Nacional
-
Ainda dentro da requisição, chama:
payload, err := MapEmissaoToNacionalPayload(req) -
Você tem uma struct
NfseNacionalPayloadpronta pra ser:-
Convertida em XML (via
encoding/xmlou template). -
Ou em JSON, se o Ambiente Nacional aceitar.
-
-
-
Chamada ao ACBr (Delphi)
Aqui entram 3 opções típicas:A. ACBr rodando como “serviço / daemon” Delphi, consumindo do banco
-
A API só marca a NFS-e como
pendente_transmissaoe grava os dados. -
Um serviço em Delphi (com ACBrNFSe) roda em background:
-
Lê as NFS-e pendentes do banco (por tenant, cliente, etc.).
-
Monta os objetos ACBr (
ACBrNFSe.NotasFiscais.Add...). -
Chama os métodos próprios (
Emitir,EnviarLoteRps, etc.). -
Recebe o XML de retorno / protocolo / número da NFS-e.
-
Atualiza as tabelas (
nfse.xml_envio,nfse.xml_retorno,nfse.protocolo,nfse.situacao = 'autorizada'ourejeitada).
-
B. ACBrMonitorPLUS via arquivos/comandos
-
A API escreve um arquivo INI/XML ou manda um comando para o ACBrMonitor (por TCP/pipe).
-
O ACBrMonitor monta o XML, envia ao provedor e retorna outro arquivo/txt/xml.
-
Sua API (ou um worker) lê esse retorno e atualiza a
nfse.
C. ACBr exposto como “serviço HTTP” próprio
-
Você monta um mini servidor HTTP em Delphi que recebe um JSON/XML equivalente ao
NfseNacionalPayload. -
A API Go faz HTTP POST pra esse serviço Delphi.
-
O serviço Delphi:
-
Converte JSON → objetos ACBr.
-
Envia pro Ambiente Nacional.
-
Devolve um JSON com status, protocolo, xml_autorizado, erros etc.
-
-
A API Go recebe esse retorno e atualiza a
nfse.
Como você já tem a aplicação Delphi com ACBr, o caminho mais limpo costuma ser:
-
Persistir NFS-e no banco.
-
Ter um serviço Delphi que processa essas NFS-e pendentes e atualiza o status.
-
-
Retorno do ACBr → atualização da nfse
-
Quando o ACBr recebe o retorno (OK ou erro):
-
Se autorizada:
-
nfse.situacao = 'autorizada' -
nfse.protocolo_envio = '...' -
nfse.xml_envio = '...' -
nfse.xml_retorno = '...' -
nfse.codigo_mensagem_retorno = null(ou código de sucesso) -
nfse.mensagem_retorno = null(ou mensagem de sucesso)
-
-
Se rejeitada:
-
nfse.situacao = 'rejeitada' -
nfse.codigo_mensagem_retorno = 'codigo_erro' -
nfse.mensagem_retorno = 'mensagem detalhada do provedor' -
Pode ou não gravar o XML de retorno conforme sua política.
-
-
-
Isso pode ser feito por:
-
Serviço Delphi atualizando direto o Postgres.
-
Ou, se você quiser mais “isolamento”: serviço Delphi chamando um endpoint da sua API (
PATCH /tenants/{tenantID}/nfse/{id}/status).
-
-
-
Resposta pro cliente da API
-
Se você fizer tudo síncrono (não muito recomendado para alta carga), pode:
-
Só finalizar a request quando tiver o retorno da prefeitura.
-
Responder já com
situacao = 'autorizada'e os dados completos.
-
-
Se você fizer assíncrono (recomendado):
-
A API responde imediatamente após o
persistircom algo como:{ "id": 123, "situacao": "pendente_transmissao" } -
O cliente depois chama
GET /tenants/{tenantID}/nfse/{id}pra ver o status atualizado (autorizada,rejeitada, etc).
-
-
2.2. Diagrama de fluxo (Mermaid)
sequenceDiagram
participant Cli as Cliente (Front/ERP)
participant API as API Go (NFSe)
participant DB as PostgreSQL (multi-tenant)
participant Svc as Serviço Delphi + ACBrNFSe
participant Pref as Ambiente Nacional NFSe
Cli->>API: POST /tenants/{tenantID}/nfse (EmissaoNfseRequest)
API->>API: MapEmissaoToDomain (Nfse + Itens)
API->>DB: BEGIN TX + SET LOCAL app.tenant_id
API->>DB: INSERT nfse / nfse_itens (situacao = pendente_transmissao)
DB-->>API: id da nfse
API->>DB: COMMIT
API-->>Cli: 201 Created (id, situacao=pendente_transmissao)
Note over API,Svc: Fluxo assíncrono recomendado (fila de transmissão)
Svc->>DB: SELECT nfse WHERE situacao = pendente_transmissao
DB-->>Svc: nfse + nfse_itens
Svc->>Svc: montar objetos ACBrNFSe
Svc->>Pref: EnviarLoteRpsEnvio (XML)
Pref-->>Svc: XML de retorno (protocolo, NFSe, erros)
Svc->>DB: UPDATE nfse (situacao, protocolo, xml_envio, xml_retorno, mensagens)
Cli->>API: GET /tenants/{tenantID}/nfse/{id}
API->>DB: SELECT nfse + nfse_itens WHERE tenant_id = ...
DB-->>API: nfse (situacao=autorizada/rejeitada)
API-->>Cli: JSON com status final e dados da NFS-e2.3. Resumo da arquitetura que você fica:
-
Domínio interno estabilizado (
nfse,nfse_itens, multi-tenant, RLS, etc.). -
DTO da API (
EmissaoNfseRequest) próximo do padrão Nacional. -
Mappers:
EmissaoNfseRequest → Nfse + Itens(para persistência).EmissaoNfseRequest → NfseNacionalPayload → XML(para ACBr/provedor).
-
ACBr/Delphi ficando na ponta “fiscal”, especialista na conversa com o Ambiente Nacional.
-
API Go sendo o “orquestrador” e sistema de registro (source of truth).