Cliente Elixir para a NFS-e Nacional: emite, consulta e cancela a nota pela Sefin Nacional, assina a DPS com o certificado A1 do emitente, importa documentos pelo ADN e lê os parâmetros municipais.
Só depende de req, decimal e saxy. Não há processo, Application nem configuração global: tudo passa por um %Nfse.Client{}.
Instalação
def deps do
[
{:nfse, "~> 0.1.0"}
]
endRequer Elixir 1.15 e OTP 26 ou mais novos. O openssl do sistema precisa estar instalado, porque é ele que abre o .pfx. Veja o guia Certificado digital.
Exemplo
Emissão do corte de cabelo de R$ 49,90 do "Studio Ana", prestadora do Simples Nacional em Salvador, na produção restrita:
{:ok, certificate} =
"certificado.pfx"
|> File.read!()
|> Nfse.Certificate.from_pfx(System.fetch_env!("NFSE_PFX_PASSWORD"))
{:ok, client} = Nfse.Client.new(certificate: certificate, environment: :restricted)
now = DateTime.truncate(DateTime.utc_now(), :second)
dps = %Nfse.DPS{
# Um minuto de folga para um relógio adiantado: o dh_emi não pode estar à frente da Sefin.
dh_emi: DateTime.add(now, -60),
ver_aplic: "studio-ana-1.0",
serie: "1",
n_dps: 42,
# A competência é a data em Brasília (UTC-3), que perto da meia-noite difere da data em UTC.
d_compet: now |> DateTime.add(-3 * 3600) |> DateTime.to_date(),
tp_emit: 1,
c_loc_emi: "2927408",
prest: %{
cnpj: "11222333000181",
reg_trib: %{op_simp_nac: 3, reg_ap_trib_sn: 1, reg_esp_trib: 0}
},
toma: %{cpf: "52998224725", x_nome: "Ana Souza"},
serv: %{
loc_prest: %{c_loc_prestacao: "2927408"},
c_serv: %{c_trib_nac: "060101", x_desc_serv: "Corte de cabelo"}
},
valores: %{
v_serv_prest: %{v_serv: Decimal.new("49.90")},
trib: %{
trib_mun: %{trib_issqn: 1, tp_ret_issqn: 1},
tot_trib: %{p_tot_trib_sn: Decimal.new("6.00")}
}
}
}
{:ok, issued} = Nfse.issue(client, dps)
issued.access_key
issued.xmlOs campos seguem o nome do XSD em snake_case, com as siglas NFSe, DFSe, NFe e DFe contadas como uma palavra só (infNFSe vira inf_nfse). Toda função pública devolve {:ok, _} ou {:error, %Nfse.Error{}}, com uma exceção: Nfse.stream_distribution/3 levanta Nfse.Error.
Guias
- Certificado digital
- Ambientes
- Emitir uma NFS-e
- Conferir após timeout
- Cancelar e registrar eventos
- Importar notas pelo ADN
- Parâmetros municipais
O que a biblioteca cobre
- Montagem, validação contra o schema e assinatura da DPS (leiaute 1.01), com o
Idcalculado - Emissão síncrona (
Nfse.issue/2) e por decisão administrativa ou judicial (Nfse.issue_by_court_order/2) - Consulta de NFS-e, conferência de uma DPS pelo
Ide busca da chave de acesso - Cancelamento e demais eventos do contribuinte (
Nfse.cancel/3,Nfse.register_event/2,Nfse.fetch_event/4) - Distribuição de documentos pelo ADN, com cursor de NSU,
stream_distribution/3e leitura dos eventos de uma nota - Parâmetros municipais do ADN: convênio, alíquotas, regimes especiais, retenções e benefícios
- Leitura estrita de NFS-e e eventos nos leiautes 1.00 e 1.01
- Reenvio seguro: uma escrita só é repetida quando o pedido nunca saiu da máquina; o resto volta com o
dps_idpara conferir (veja Conferir após timeout)
O que fica de fora
- As APIs de cada município. Esta biblioteca fala só com o sistema nacional.
- A geração do DANFSe (o PDF da nota). Para isso existe a biblioteca
nfse_danfse. - As operações de alteração dos parâmetros municipais (
POST .../{idManut}), que pertencem ao município.
O que ainda não está confirmado
Estes pontos dependem da execução contra a produção restrita, descrita na próxima seção, e estão também nas seções "O que não está confirmado" de cada módulo:
- O caminho da Sefin na produção restrita:
/API/SefinNacional, como no portal gov.br, ou/SefinNacional. - A assinatura da DPS em RSA-SHA1 ou RSA-SHA256.
- Se o ME/EPP do Simples Nacional informa
pAliq. - A série da DPS com
^ou$, que o padrão do XSD permite ao pé da letra. - Emoji no
xNome. O XSD não tem o padrão Latin-1 nesse campo e a biblioteca o segue; se a Sefin recusa, a recusa chega como:rejected. - Campos de texto só com espaços em branco.
- A recusa de uma DPS repetida pelo
Id, de que depende o reenvio depois de uma conferência negativa (veja Conferir após timeout). Se a Sefin a aceitar, a nota sai duas vezes. - O formato da resposta de
GET /nfse/{chave}/eventos/{tipo}/{sequência}(Nfse.fetch_event/4), documentado com o mesmo campoeventoXmlGZipB64do registro, e o deGET /contribuintes/NFSe/{chave}/Eventos(Nfse.events/2). - O fluxo de decisão judicial, que só se prova com um município que cadastre a decisão e autorize o contribuinte. Dentro dele, o
nDFSe"0"que o manual exige e o padrão do XSD recusa ([1-9]{1}[0-9]{0,12}): a biblioteca segue o manual. - As chaves
beneficioeregimesEspeciaisdos parâmetros municipais, que o swagger não mostra. A cadeia do certificado no handshake. A biblioteca lê a cadeia do
.pfx, mas o TLS envia só a folha. Se a Sefin recusar o handshake pedindo a cadeia, enviarcert: [folha | cadeia](veja "O que não está confirmado" emNfse.Client).
Rodando contra a produção restrita
A suíte test/restricted/journey_test.exs conversa com a produção restrita da Sefin e do ADN, onde os documentos não têm validade jurídica, usando um certificado ICP-Brasil A1 de verdade. Ela fica fora do mix test e precisa de três variáveis de ambiente:
| Variável | Conteúdo |
|---|---|
NFSE_PFX_PATH | caminho do arquivo .pfx do emitente |
NFSE_PFX_PASSWORD | senha do .pfx |
NFSE_C_LOC_EMI | código IBGE de sete dígitos do município do emitente |
NFSE_PFX_PATH=/caminho/certificado.pfx NFSE_PFX_PASSWORD=... NFSE_C_LOC_EMI=2927408 mix test --only restricted
O CNPJ do emitente sai do certificado. Sem as variáveis, a suíte falha e diz quais faltam. Cada execução emite e cancela uma nota de teste; o tomador é fictício.