# `Nfse.Client`
[🔗](https://github.com/ideia4it/nfse/blob/v0.1.0/lib/nfse/client.ex#L1)

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

| `environment` | Sefin | ADN |
|---|---|---|
| `:production` | `https://sefin.nfse.gov.br/SefinNacional` | `https://adn.nfse.gov.br` |
| `:restricted` | `https://sefin.producaorestrita.nfse.gov.br/API/SefinNacional` | `https://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"

# `environment`

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

# `service`

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

# `t`

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

# `message`

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

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

# `messages`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
