# Partner API: taxas, correlacao no extrato e MED (design)

Data: 2026-07-14. Decisoes do dono na sessao: escopo completo da API de taxas
(catalogo + cobradas + webhook), correlacao exposta no extrato, endpoint MED
agora, tarifas pix_out/ted_out ativadas SOMENTE em HML.

Contexto: a Vulci (Herbeth) pediu no documento "API Vulci" (a) endpoint de
transacoes tarifarias, (b) exemplos de payload de webhook, (c) endpoint para
abrir intervencao MED, e perguntou como correlacionar a tarifa com a transacao
principal. A gestao de tarifas permanece 100% na plataforma Monetarie
(fee_configs, admin-only); o parceiro so le e recebe notificacao.

## O que ja existe (evidencia na sessao)

- Onboarding completo com numero de conta gerado por NOS: POST /customers gera
  numero via sequencia PG `account_number_seq` + DV modulo 11 + IBAN
  (`use_cases/accounts/open_account.ex`, `account_number_generator.ex`);
  KYC nao-delegavel roda no ato (active/blocked).
- Tarifa JA e lancamento separado: `FeeCharger.record_statement_entry/2` cria
  `account_entries` com `category: "fee"`; correlacao interna =
  `fee_transactions.origin_transaction_id` (E2E/ID da principal); campo `fee`
  no `render_status` do PIX (`Fees.charged_fee_centavos/1`).
- MED: ponte pronta (`Provider.create_infraction/2` -> NATS `dict.api.request`
  action `create_infraction` na cabine DICT), exposta so no portal v2 (staff).

## Entregas

### 1. API de taxas (partner_v1, leitura)

- `GET /api/partner/v1/fees` — catalogo de tarifas ATIVAS aplicaveis
  (fee_type, clientType, amounts em centavos, min/max, chargingModel,
  freeTransactionsPerMonth). Filtro opcional `account_id` (resolve hierarquia
  conta -> usuario -> global, como o `FeeCalculator`). Sem expor COSIF nem
  campos internos.
- `GET /api/partner/v1/fees/charges` — tarifas cobradas do parceiro
  (fee_transactions), filtros: `account_id`, `origin_transaction_id`,
  `from`/`to`, paginacao. Render: id, feeType, amount (centavos),
  originTransactionId, originTransactionType, originAmount, status
  (posted/reversed), chargedAt, accountId. Escopo por parceiro via
  accounts -> users.partner_id (NUNCA vazar tarifa de outro parceiro; id
  desconhecido/cross-partner = lista vazia/404, sem vazamento de existencia).
- Permissao: `fee:read` (nova) no PermissionEnforcer; conceder as chaves
  existentes em HML no deploy.

### 2. Webhook `fee.charged`

- Novo evento em `Webhook.@valid_events` + producer no `FeeCharger` apos
  persistir a `FeeTransaction` (fail-soft, mesmo padrao dos demais).
- Payload (entregue em camelCase, dinheiro em CENTAVOS): feeTransactionId,
  accountId, feeType, amount, originTransactionId, originTransactionType,
  originalAmount, chargedAt. ATENCAO unidade: o pipeline
  `money_fields_to_cents` converte base_units -> centavos nas chaves
  `amount`/`originalAmount`; `fee_transactions` guarda CENTAVOS, entao o
  producer entrega em base_units (x100) para o pipeline emitir centavos
  (contrato unico de todos os webhooks).
- `event_id` deterministico = id da fee_transaction (dedup por webhook).
- Tenant: `account_id` no payload ORIGINAL (roteamento por parceiro ja usa o
  payload pre-canonicalizacao).

### 3. Correlacao no extrato

- `GET /accounts/:id/statement`: lancamentos `type = "fee"` ganham
  `feeTransactionId` (de `account_entries.metadata.fee_transaction_id`) e
  `originTransactionId` (lookup em lote em `fee_transactions`, sem N+1).

### 4. MED partner (abrir intervencao)

- `POST /api/partner/v1/pix/infractions` — abre infracao MED: valida que a
  conta/transacao pertence ao parceiro, delega a `Provider.create_infraction`
  (mesma ponte NATS do v2). 202 accepted com dados da cabine.
- `GET /api/partner/v1/pix/infractions/:id` — consulta (get_infraction).
- Permissoes: POST = `pix:write`, GET = `pix:read` (reusa escopos existentes
  das chaves).
- Acompanhamento continua pelos webhooks `pix.infraction.created/resolved`.

### 5. Dados em HML (sem tocar PROD)

- Ativar `is_active=true` nas fee_configs `pix_out` e `ted_out` (pf/pj) em
  HML para a Vulci testar tarifa real no extrato/webhook. PROD permanece tudo
  inativo (decisao de negocio pendente do dono).

### 6. Docs e resposta a Vulci

- Portal do parceiro (pt): pagina Tarifas ganha os novos endpoints + evento
  fee.charged com payload real; pagina Webhooks atualizada; Postman.
- Resposta ao documento da Vulci: tarifa E transacao separada; correlacao =
  `originTransactionId`; exemplos de payload por evento ja publicados no
  portal; MED disponivel; boletos = roadmap (decisao anterior do dono).

## Fora de escopo

- Endpoint para o parceiro CRIAR transacao tarifaria (gestao e nossa, decisao
  do dono). Tarifa avulsa segue pelo admin (`charge-now`) e batch mensal.
- Boletos (roadmap, decisao anterior).
- Ativacao de tarifas em PROD.

## Testes

TDD em cima de: catalogo (hierarquia e escopo), charges (escopo por parceiro,
filtros, unidade centavos), webhook fee.charged (payload canonico em centavos,
dedup por event_id, fail-soft), extrato (correlacao presente so em type=fee,
lote sem N+1), MED (escopo, delegacao, erros da cabine sem vazar interna).
Validacao viva em HML com a chave do parceiro antes de PROD.
