# Partner API: auditoria completa + correções (2026-07-16)

Mandato do dono: investigação completa do Core Partner API depois do erro de consulta DICT do parceiro Herbeth (Vulci) e da reclamação de dados incompletos de TED. Garantir que todo fluxo de PIX e TED retorne os dados de pagador e recebedor nos campos corretos, com prova empírica em HML.

## Diagnóstico (provado empiricamente em HML, 16/07)

Evidência primária: conta 10024216 (Herbeth, restaurada: `users.partner_id` tinha sido perdido pelo re-ETL de 08/07), chave EVP `4bf4c483-eaf7-4a8a-be24-f3834c2026eb` (existe no DICT: titular Qa Lifecycle, KANASTRA CFI, ISPB 49288113), PIX vivo de R$ 0,01 (`PIXOUT20260716c8ca32921e03b85a9fd5`), TEDs reais do acervo (`TED20260713c320043c3b0168c7c09a`, `SPBCRSTR20260710990000222`).

1. **DICT lookup quebrado**: `dict_lookup` do partner chama `lookup_key/2` sem documento do pagador; o DICT v2.11.0 exige o header `PI-PayerId`; a cabine falha com `:missing_pi_payer_id`, mascara como `not_found` e o Core devolve HTTP 200 `{"data":{"status":"not_found","data":null}}`. Prova A/B no nó vivo: com documento, a mesma chave resolve.
2. **PIX-out do partner deterministicamente quebrado**: `send_pix` não envia `sender_document`; a cabine (`require_debtor_document!`) falha ao construir a pacs.008, o processor dá NAK (falha determinística tratada como transitória), o redelivery bate na queima de E2E (`:e2e_already_used`) e rejeita. O parceiro vê `rejected` com `errorReason: null`. Dinheiro seguro (nada foi ao BACEN; holds liberados).
3. **Contraparte descartada em toda a superfície de leitura**: extrato, comprovante, status de PIX e status de TED do partner usam serializers próprios que nunca leem `transactions.metadata` nem `counterparty_name`. O comprovante do partner não tem blocos payer/receiver/pix (divergiu do canônico `Receipts.Payload` usado por IB/admin).
4. **TED enviada não persiste o recebedor**: o create grava `metadata: {partner_id}` só; os campos `recipient_*` vão apenas no fio SPB. Irrecuperável em leituras posteriores (provado nas 2 TEDs reais do acervo).
5. **Prefixo divergente na TED recebida**: `spb_handler` grava `sender_*`; `Receipts.Payload.metadata_party` lê só `payer_*` — comprovante de admin/IB também perde a contraparte dessas TEDs.
6. **Higiene de erro**: `show_med` mascara qualquer falha como 404; `inspect()` de termo Elixir vaza no JSON (webhooks replay, customers create/add_account, decode BR Code).
7. **Webhooks sem contraparte** (pix.payout.*, ted.*): fica para depois — os dispatch sites vivem em `pix_handler.ex`/`spb_handler.ex`, em edição pela sessão paralela.

## Correções (TDD, arquivos disjuntos da sessão paralela)

### Core `core/backend`
- `partner_v1/pix_controller.ex`
  - `dict_lookup`: resolve documento do pagador (query `payerDocument`/`payer_document` > `accountId` do parceiro (titular) > documento do partner) e chama `lookup_key/3`; mapeia `status=not_found` para 404 com erro claro; `status=error` segue no `cabin_app_error` (422); transporte no `cabin_error`.
  - `send_pix`: inclui `sender_document` e `sender_name` (titular da conta origem) no payload à cabine, como o V2 faz.
  - `render_status`: bloco `recipient` (name/document/ispb + key) lido do metadata quando existir (aditivo).
  - `show_med`: erro de transporte via `cabin_error`, erro de aplicação via `cabin_app_error`, not_found real 404.
  - `brcode_error_message`: sem `inspect` no corpo.
- `partner_v1/transfers_controller.ex`: persiste `recipient_name/document/ispb/agency/account` no `metadata` da TED no create; `render_status` devolve o bloco `recipient` (aditivo).
- `partner_v1/accounts_controller.ex`: extrato ganha `counterparty` por lançamento (normalizado por direção: payer_*/sender_* na entrada, recipient_* na saída, fallback `counterparty_name`); comprovante passa a montar payer/receiver/pix pelo canônico `Receipts.Payload.build/5` (aditivo: chaves atuais preservadas).
- `use_cases/receipts/payload.ex`: `metadata_party` aceita `sender_*` como alias de `payer_*` (cura admin/IB também); expõe helper público para os serializers do partner.
- `v2/merchant_portal_controller.ex`: `dict_lookup` passa o documento do merchant.
- `partner_v1/customers_controller.ex`, `webhooks_controller.ex`: mensagens de erro sem `inspect`.

### Cabine `pix/backend`
- `dict_service/keys.ex`: `:missing_pi_payer_id` vira razão própria (`:missing_payer_document`), não catch-all.
- `dict_service/nats/dict_lookup_responder.ex`: `not_found` SÓ para not_found real; falha vira `%{"status" => "error"}` com razão legível.
- `settlement_service/workers/core_event_processor.ex`: falha de construção do XML (documento ausente) é DETERMINÍSTICA: `PAYMENT_REJECTED` com razão clara na primeira entrega, sem NAK (evita o segundo round e o `:e2e_already_used` enganoso).

### Fora do escopo desta sessão (handoff)
- Webhooks de liquidação com contraparte (`pix_handler.ex`/`spb_handler.ex` em edição paralela).
- Persistir `error_reason` no Core a partir do `transaction.error` (mesmo motivo).
- Deploy HML/PROD: código fica pronto; deploy só com OK do dono.

## Validação
- RED antes de cada fix nos testes dos controllers partner + payload + cabine.
- Suites: partner_v1 completas, receipts/v2 (paridade admin==IB), dict_service, settlement_service.
- Pós-deploy (fora desta sessão): repetir o fluxo vivo Herbeth→KANASTRA (lookup com dados completos do titular, PIX R$ 0,01 liquidado ou rejeição com motivo legível) e uma TED com `recipient` no status/extrato/comprovante.
