Nfse.Client (nfse v0.1.0)

Copy Markdown View Source

Cliente HTTP da NFS-e Nacional, autenticado por mTLS com o certificado A1 do emitente.

Todo o estado vive no %Nfse.Client{}: certificado, ambiente, tempo de espera e opções do Req. Não há processo nem configuração global.

Ambientes

environmentSefinADN
:productionhttps://sefin.nfse.gov.br/SefinNacionalhttps://adn.nfse.gov.br
:restrictedhttps://sefin.producaorestrita.nfse.gov.br/API/SefinNacionalhttps://adn.producaorestrita.nfse.gov.br

Reenvio

Uma escrita (:post) só é repetida quando o pedido nunca saiu da máquina: host que não resolve (:nxdomain), conexão recusada ou rede inalcançável antes do envio, e as recusas do pool HTTP/2 que ocorrem antes de transmitir. Qualquer outra falha é ambígua, porque a Sefin pode já ter emitido a nota: timeout, conexão fechada e toda resposta HTTP, 5xx inclusive, voltam como Nfse.Error para o chamador conferir a DPS em vez de reenviá-la. Repetir nesses casos duplicaria a nota fiscal.

Leituras (:get e :head) são repetidas em falhas transitórias (408, 429, 500, 502, 503, 504, timeout, conexão recusada ou fechada, :nxdomain).

req_options não consegue religar o reenvio de uma escrita: a política de :post vale mesmo que retry: seja informado ali.

Opções do Req

req_options é mesclado por último e vence os padrões. Um finch: [name: MyFinch] ali substitui o pool inteiro, porque o Req não aceita nome junto com opções de pool. Quem faz isso precisa iniciar o próprio Finch com o certificado em conn_opts: [transport_opts: [cert: ..., key: ...]], senão o mTLS não acontece.

O que não está confirmado

  • a cadeia do certificado no handshake: Nfse.Certificate lê a cadeia do .pfx, mas o TLS envia só a folha. Se a Sefin recusar o handshake pedindo a cadeia, enviar cert: [folha | cadeia] em transport_opts; até a biblioteca fazer isso, quem precisar inicia o próprio Finch assim, como em "Opções do Req"

Summary

Functions

Normaliza um item de erros ou de alertas, em PascalCase ou camelCase, como Nfse.Error.message/0.

Lê as mensagens de erro de um corpo de resposta.

Monta o cliente.

Faz a requisição ao serviço e classifica a resposta.

Valor do campo tpAmb do ambiente, como texto: 1 em produção e 2 em produção restrita.

Types

environment()

@type environment() :: :production | :restricted

service()

@type service() :: :sefin | :adn

t()

@type t() :: %Nfse.Client{
  certificate: Nfse.Certificate.t(),
  environment: environment(),
  receive_timeout: pos_integer(),
  req_options: keyword()
}

Functions

message(error)

@spec message(map()) :: Nfse.Error.message()

Normaliza um item de erros ou de alertas, em PascalCase ou camelCase, como Nfse.Error.message/0.

messages(arg1)

@spec messages(map()) :: [Nfse.Error.message()]

Lê as mensagens de erro de um corpo de resposta.

Aceita erros/Erros (lista), erro/Erro (objeto) e, na falta deles, um mensagem de nível superior. Os campos podem vir em PascalCase ou camelCase.

new(opts)

@spec new(keyword()) :: {:ok, t()} | {:error, Nfse.Error.t()}

Monta o cliente.

Opções: :certificate e :environment (obrigatórias), :receive_timeout (padrão 60_000) e :req_options (padrão []). Recusa, sem rede, o certificado vencido e o que não serve para clientAuth.

Uma opção obrigatória ausente ou um :environment fora de :production e :restricted volta como %Nfse.Error{kind: :validation}, com os erros juntos em details, nos caminhos certificate e environment (:required ou :inclusion).

request(client, service, method, path, opts \\ [])

@spec request(t(), service(), :get | :head | :post, String.t(), keyword()) ::
  {:ok, Req.Response.t()} | {:error, Nfse.Error.t()}

Faz a requisição ao serviço e classifica a resposta.

Opções: :json (corpo), :params (query string) e :accept, uma lista de status que voltam como {:ok, resp} mesmo fora de 2xx, para APIs que respondem 404 com corpo em vez de erro.

Uma resposta que chegou com um JSON que não se decodifica não é falha de transporte: com status 2xx ou aceito, volta como :unexpected com o status e details em {:invalid_json, exceção}; com outro status, volta pelo kind do status, sem mensagens.

tp_amb(client)

@spec tp_amb(t()) :: String.t()

Valor do campo tpAmb do ambiente, como texto: 1 em produção e 2 em produção restrita.