README.md

# NXGate SDK para Elixir

SDK oficial da **NXGATE** para integração com a API PIX em aplicações Elixir.

## Funcionalidades

- Geração de cobranças PIX (cash-in) com QR Code
- Saques PIX (cash-out)
- Consulta de saldo e transações
- Gerenciamento automático de token OAuth2 via GenServer
- Assinatura HMAC-SHA256 automática (quando configurado)
- Parser de webhooks para eventos PIX
- Retry automático com backoff em erros 503
- Zero dependências HTTP externas (usa `:httpc` da stdlib Erlang)

## Requisitos

- Elixir ~> 1.14
- Erlang/OTP ~> 25

## Instalação

Adicione `nxgate` às suas dependências no `mix.exs`:

```elixir
def deps do
  [
    {:nxgate, "~> 0.1.0"}
  ]
end
```

## Configuração

### Início Rápido

```elixir
# Iniciar o client (geralmente no seu application supervisor)
{:ok, client} = NXGate.start_link(
  client_id: "nxgate_xxx",
  client_secret: "secret",
  hmac_secret: "opcional"  # opcional - ativa assinatura HMAC
)
```

### Uso com Supervisor

Adicione o client ao seu supervisor para gerenciamento automático do ciclo de vida:

```elixir
defmodule MyApp.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      {NXGate,
       client_id: System.fetch_env!("NXGATE_CLIENT_ID"),
       client_secret: System.fetch_env!("NXGATE_CLIENT_SECRET"),
       hmac_secret: System.get_env("NXGATE_HMAC_SECRET"),
       name: :nxgate}
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

### Opções de Configuração

| Opção | Tipo | Obrigatório | Padrão | Descrição |
|-------|------|:-----------:|--------|-----------|
| `client_id` | `string` | Sim | - | ID do client OAuth2 |
| `client_secret` | `string` | Sim | - | Secret do client OAuth2 |
| `hmac_secret` | `string` | Não | `nil` | Secret para assinatura HMAC-SHA256 |
| `base_url` | `string` | Não | `https://api.nxgate.com.br` | URL base da API |
| `timeout` | `integer` | Não | `15000` | Timeout em milissegundos |
| `max_retries` | `integer` | Não | `2` | Máximo de retentativas em erro 503 |

## Uso

### Gerar Cobrança PIX (Cash-in)

Gera uma cobrança PIX e retorna o QR Code para pagamento:

```elixir
{:ok, charge} = NXGate.pix_generate(client, %{
  valor: 100.00,
  nome_pagador: "João da Silva",
  documento_pagador: "12345678901",
  webhook: "https://meusite.com/webhook",
  descricao: "Pagamento do pedido #123"
})

# Resposta:
# %{
#   "status" => "success",
#   "message" => "...",
#   "paymentCode" => "00020126...",
#   "idTransaction" => "abc123",
#   "paymentCodeBase64" => "data:image/png;base64,..."
# }
```

#### Parâmetros

| Parâmetro | Tipo | Obrigatório | Descrição |
|-----------|------|:-----------:|-----------|
| `valor` | `float` | Sim | Valor em reais |
| `nome_pagador` | `string` | Sim | Nome do pagador |
| `documento_pagador` | `string` | Sim | CPF ou CNPJ do pagador |
| `forcar_pagador` | `boolean` | Não | Forçar dados do pagador |
| `email_pagador` | `string` | Não | Email do pagador |
| `celular` | `string` | Não | Celular do pagador |
| `descricao` | `string` | Não | Descrição da cobrança |
| `webhook` | `string` | Não | URL de callback |
| `magic_id` | `string` | Não | ID mágico para referência |
| `api_key` | `string` | Não | Chave de API |
| `split_users` | `list` | Não | Split de pagamento |

#### Split de Pagamento

```elixir
{:ok, charge} = NXGate.pix_generate(client, %{
  valor: 100.00,
  nome_pagador: "Maria Santos",
  documento_pagador: "98765432100",
  split_users: [
    %{username: "loja_principal", percentage: 90.0},
    %{username: "parceiro", percentage: 10.0}
  ]
})
```

### Saque PIX (Cash-out)

Realiza uma transferência PIX para a chave informada:

```elixir
{:ok, withdrawal} = NXGate.pix_withdraw(client, %{
  valor: 50.0,
  chave_pix: "joao@email.com",
  tipo_chave: :email,
  webhook: "https://meusite.com/webhook"
})

# Resposta:
# %{
#   "status" => "success",
#   "message" => "...",
#   "internalreference" => "ref_xyz"
# }
```

#### Tipos de Chave PIX

| Atom | Valor | Descrição |
|------|-------|-----------|
| `:cpf` | `CPF` | CPF do destinatário |
| `:cnpj` | `CNPJ` | CNPJ do destinatário |
| `:phone` | `PHONE` | Telefone do destinatário |
| `:email` | `EMAIL` | Email do destinatário |
| `:random` | `RANDOM` | Chave aleatória |

### Consultar Saldo

```elixir
{:ok, balance} = NXGate.get_balance(client)

# Resposta:
# %{
#   "balance" => 1000.00,
#   "blocked" => 50.00,
#   "available" => 950.00
# }
```

### Consultar Transação

```elixir
# Cash-in
{:ok, tx} = NXGate.get_transaction(client, :cash_in, "id_transacao")

# Cash-out
{:ok, tx} = NXGate.get_transaction(client, :cash_out, "id_transacao")

# Resposta:
# %{
#   "idTransaction" => "...",
#   "status" => "PAID",
#   "amount" => 100.0,
#   "paidAt" => "2026-01-15T10:30:00Z",
#   "endToEnd" => "..."
# }
```

## Webhooks

O módulo `NXGate.Webhook` facilita o processamento de notificações recebidas da NXGATE.

### Configuração no Phoenix

```elixir
defmodule MyAppWeb.WebhookController do
  use MyAppWeb, :controller

  def nxgate(conn, params) do
    case NXGate.Webhook.parse(params) do
      {:ok, %{type: "QR_CODE_COPY_AND_PASTE_PAID", category: :cash_in} = event} ->
        # Pagamento PIX recebido
        IO.inspect(event.data.amount, label: "Valor recebido")
        IO.inspect(event.data.tx_id, label: "ID da transação")
        json(conn, %{ok: true})

      {:ok, %{type: "QR_CODE_COPY_AND_PASTE_REFUNDED"} = event} ->
        # Pagamento estornado
        handle_refund(event)
        json(conn, %{ok: true})

      {:ok, %{type: "PIX_CASHOUT_SUCCESS"} = event} ->
        # Saque realizado com sucesso
        IO.inspect(event.data.id_transaction, label: "Saque confirmado")
        json(conn, %{ok: true})

      {:ok, %{type: "PIX_CASHOUT_ERROR"} = event} ->
        # Erro no saque
        handle_cashout_error(event)
        json(conn, %{ok: true})

      {:ok, %{type: "PIX_CASHOUT_REFUNDED"} = event} ->
        # Saque estornado
        handle_cashout_refund(event)
        json(conn, %{ok: true})

      {:error, reason} ->
        Logger.warning("Webhook NXGATE inválido: #{inspect(reason)}")
        conn |> put_status(400) |> json(%{error: "invalid payload"})
    end
  end
end
```

### Tipos de Evento

#### Cash-in (PIX Recebido)

| Tipo | Descrição |
|------|-----------|
| `QR_CODE_COPY_AND_PASTE_PAID` | QR Code pago com sucesso |
| `QR_CODE_COPY_AND_PASTE_REFUNDED` | QR Code estornado |

#### Cash-out (PIX Enviado)

| Tipo | Descrição |
|------|-----------|
| `PIX_CASHOUT_SUCCESS` | Saque realizado com sucesso |
| `PIX_CASHOUT_ERROR` | Erro no saque |
| `PIX_CASHOUT_REFUNDED` | Saque estornado |

### Funções Auxiliares

```elixir
# Verificar se é evento de cash-in
NXGate.Webhook.cash_in?("QR_CODE_COPY_AND_PASTE_PAID")  # true

# Verificar se é evento de cash-out
NXGate.Webhook.cash_out?("PIX_CASHOUT_SUCCESS")  # true

# Listar todos os tipos conhecidos
NXGate.Webhook.known_types()
```

## Assinatura HMAC

Quando o `hmac_secret` é configurado, todas as requisições incluem automaticamente os seguintes headers:

| Header | Descrição |
|--------|-----------|
| `X-Client-ID` | Identificador do client |
| `X-HMAC-Signature` | Assinatura HMAC-SHA256 codificada em Base64 |
| `X-HMAC-Timestamp` | Timestamp ISO 8601 da requisição |
| `X-HMAC-Nonce` | String única por requisição |

A string de assinatura é composta por:

```
METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY
```

## Tratamento de Erros

Todas as funções retornam `{:ok, result}` ou `{:error, %NXGate.Error{}}`.

```elixir
case NXGate.pix_generate(client, params) do
  {:ok, charge} ->
    # Sucesso
    IO.puts("QR Code: #{charge["paymentCode"]}")

  {:error, %NXGate.Error{reason: :validation_error} = err} ->
    # Erro de validação dos parâmetros
    IO.puts("Parâmetros inválidos: #{err.message}")

  {:error, %NXGate.Error{reason: :auth_error}} ->
    # Erro de autenticação
    IO.puts("Verifique suas credenciais")

  {:error, %NXGate.Error{reason: :api_error, status_code: code} = err} ->
    # Erro retornado pela API
    IO.puts("Erro HTTP #{code}: #{err.message}")

  {:error, %NXGate.Error{reason: :connection_error}} ->
    # Erro de rede/conexão
    IO.puts("Verifique sua conexão")

  {:error, %NXGate.Error{reason: :timeout}} ->
    # Timeout na requisição
    IO.puts("A requisição expirou")

  {:error, %NXGate.Error{reason: :max_retries}} ->
    # Serviço indisponível após retentativas
    IO.puts("Serviço temporariamente indisponível")
end
```

### Tipos de Erro

| Reason | Descrição |
|--------|-----------|
| `:validation_error` | Parâmetros de entrada inválidos |
| `:auth_error` | Falha na autenticação OAuth2 |
| `:api_error` | Erro retornado pela API NXGATE |
| `:connection_error` | Erro de rede/conexão |
| `:timeout` | Timeout na requisição |
| `:max_retries` | Máximo de retentativas atingido (503) |

## Retry Automático

O SDK realiza retry automático com backoff exponencial quando recebe HTTP 503 (Service Unavailable):

- **1a tentativa**: aguarda 1 segundo
- **2a tentativa**: aguarda 2 segundos
- Após esgotar as tentativas, retorna `{:error, %NXGate.Error{reason: :max_retries}}`

O número máximo de retentativas pode ser configurado via opção `:max_retries`.

## Gerenciamento de Token

O token OAuth2 é gerenciado automaticamente pelo `NXGate.TokenManager`:

- O token é obtido automaticamente ao iniciar o client
- Renovação automática 5 minutos antes da expiração
- Em caso de falha na renovação, nova tentativa após 5 segundos
- Forçar renovação manualmente: `NXGate.refresh_token(client)`

## Desenvolvimento

```bash
# Clonar o repositório
git clone https://github.com/nxgate/sdk-elixir.git
cd sdk-elixir

# Instalar dependências
mix deps.get

# Executar testes
mix test

# Verificar formatação
mix format --check-formatted
```

## Licença

Este projeto está licenciado sob a licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.