# Chamados de integração #108 a #111: análise, correções e respostas propostas

Data: 2026-07-16 (noite). Fontes: código vivo do monorepo, prova empírica em HML,
Manual de Segurança do PIX do BACEN (§4.2) e referência autorizada de produção
`/Users/luizpenha/coreproviders` (AVIV, gestão completa de QR Code).

Estado: correções commitadas LOCALMENTE em `4cc36698` (pix) e `45237e34` (core),
sem push e sem deploy. Zero migrations. Suítes: settlement_service QR 184/0,
shared 41/0, core (spb + partner pix + webhooks + merchant portal) 97/0.

---

## #108: consulta de chave PIX externa devolvendo 404

**Diagnóstico (duas causas independentes, ambas mapeadas):**

1. Defeito NOSSO, já corrigido e deployado em 16/07 (HML e PROD): a consulta de
   chave do parceiro não enviava o documento do pagador (PI-PayerId, obrigatório
   no DICT v2.11.0 para lookup-to-pay). Sem ele o BACEN devolve not_found para
   chave VÁLIDA. Provado vivo hoje: a chave EVP da KANASTRA
   (`4bf4c483-eaf7-4a8a-be24-f3834c2026eb`) devolvia not_found e agora resolve
   200 com titular e `end_to_end_id`.
2. A chave de teste em si: em HOMOLOGAÇÃO o DICT do BACEN só conhece chaves
   registradas no DICT de homolog. Uma chave "aleatória do grupo de Dev Pix"
   (provavelmente de produção) não existe lá, e o 404 estruturado
   `{"error":{"status":404,"code":"key_not_found"}}` é o contrato correto.

**Correção adicional nesta sessão (`45237e34`):** no merchant portal, erro de
aplicação da cabine (DICT indisponível etc.) respondia 200 "de sucesso" com corpo
de erro; agora responde 422 com motivo legível. Elimina a mesma classe de
confusão ("não achei" versus "deu erro") em outra superfície.

**Resposta proposta ao cliente:**
"Identificamos e corrigimos em 16/07 um defeito nosso que fazia consultas de
chaves VÁLIDAS retornarem not_found (faltava o documento do pagador exigido pelo
DICT v2.11.0). Por favor refaçam o teste. Importante: em homologação, o DICT do
BACEN só resolve chaves registradas no ambiente de homologação; uma chave de
produção retornará 404 `key_not_found`, que é o comportamento esperado. Para
teste, a chave EVP `4bf4c483-eaf7-4a8a-be24-f3834c2026eb` (KANASTRA, ambiente de
homolog) resolve com titular e end_to_end_id. Quando a resposta for 404 com
`code: key_not_found`, a chave não existe no DICT; erros de infraestrutura agora
retornam 422/5xx com motivo, nunca 404."

---

## #109: QR Code sem valor (valor em aberto)

**Avaliação (nossa plataforma, AVIV e BACEN concordam):**

- QR **estático**: valor é OPCIONAL. Sem valor, a tag 54 nem é emitida no EMV e
  o pagador digita o valor no app dele. É a forma nativa de "valor em aberto".
  Nosso motor (cabine, `generate_static_qr`) já suporta; AVIV idem.
- QR **dinâmico** (cobrança): valor é OBRIGATÓRIO. Nossa cabine exige
  (contexto + changeset), a AVIV exige nos dois níveis dela, e no RECEBIMENTO
  QR dinâmico sem valor é rejeitado (AVIV rejeita com AC04: "QRDN exige valor").
  Existe na especificação BACEN o campo `modalidadeAlteracao` (pagador pode
  alterar o valor), que nosso payload já emite quando configurado, mas o valor
  original continua obrigatório.

**Gap identificado:** a Partner API hoje só expõe cobrança dinâmica
(`POST /pix/charges`, valor obrigatório) e CobV; NÃO expõe geração de QR
estático. Ou seja, o cliente não tem como gerar o QR de valor em aberto pela
API dele. Proposta (aguarda OK do dono): endpoint
`POST /api/partner/v1/pix/qrcodes/static` com `pixKey` opcional (default = chave
da conta) e `amount` opcional, delegando ao encoder estático existente. Esforço
pequeno, sem migration.

**Resposta proposta ao cliente:**
"Para cobrança dinâmica (QR com URL de payload) o valor é obrigatório por
especificação do arranjo: QR dinâmico sem valor é rejeitado no recebimento pelos
PSPs. Valor em aberto é suportado pelo QR ESTÁTICO, no qual a tag de valor não é
emitida e o pagador digita o valor. Estamos disponibilizando a geração de QR
estático na Partner API; avisaremos quando estiver publicada."
(Enviar esta última frase somente após o OK do dono para o endpoint.)

---

## #110: webhook ted.received sem dados do titular de origem

**Diagnóstico:** procede. O payload levava só `senderIspb` e `senderName`. Os
demais dados do remetente (documento, agência, conta) JÁ estavam persistidos no
`InboundCredit` (colunas `debtor_*`) e alimentavam o extrato interno via
`payer_metadata`, mas não iam no webhook. O análogo `pix.received` já entrega
`payerDocument`.

**Correção (`45237e34`, TDD):** `ted.received` agora entrega também
`senderDocument`, `senderAgency` e `senderAccount` (aditivo, não quebra
consumidores atuais). Documentação do portal do parceiro atualizada (pt/en/es).
Observação: eventos emitidos ANTES do deploy não são reemitidos.

**Resposta proposta ao cliente:**
"Implementado. O evento `ted.received` passa a incluir `senderDocument`
(CPF/CNPJ), `senderAgency` e `senderAccount` do titular da conta de origem, além
dos já existentes `senderName` e `senderIspb`. A mudança é aditiva (nenhum campo
atual muda). Publicaremos em breve e a documentação do portal já reflete o novo
payload."

---

## #111: URL do QR dinâmico devolvendo JSON em vez do JWS puro

**Diagnóstico: o cliente tem razão.** Prova empírica na URL de exemplo do
chamado: respondíamos `application/json` com envelope
`{status, jws, valor, txid, devedor, ..., assinatura}` (forma herdada do
LegadoPIX `RetLerDinamico`). O Manual de Segurança do PIX §4.2 define que o
conteúdo da URL É a estrutura JWS em Compact Serialization
(`header.payload.assinatura`); a referência AVIV serve exatamente isso com
content-type `application/jose` e nunca emite JSON não assinado em produção.

**Correção (`4cc36698`, TDD):**
- `GET /qr/v2/:access_token` responde o JWS compacto PURO, content-type
  `application/jose`. Status 200 para ATIVA/CONCLUIDA; 410 Gone para
  REMOVIDA_* (o corpo do 410 também é o JWS assinado com o status); 404 token
  desconhecido; 503 sem certificado CERTQRC (nunca serve payload sem assinar).
- Rotas públicas `/qr/v2` e `/qrc/jwks` saíram do pipeline com
  `accepts ["json"]`, que respondia 406 a `Accept: application/jose` de PSP
  pagador antes de chegar ao controller (gap latente achado na análise).
- A assinatura NÃO mudou: RS512 é permitido pelo manual ("RS256 ou superior";
  PS256/PS512 são recomendados), header com `alg`+`jku`+`kid`+`x5t` é o mínimo
  exigido, e o JWK Set público segue em `GET /qrc/jwks`. Quem já verificava o
  JWS de dentro do envelope continua verificando o mesmo JWS.

**Validação pós-deploy:** `curl` na URL do exemplo deve voltar corpo iniciando
com `eyJ` (3 segmentos separados por ponto) e `content-type: application/jose`.

**Resposta proposta ao cliente:**
"Confirmado e corrigido. A URL do payload do QR dinâmico passa a responder
somente o JWS em Compact Serialization (content-type `application/jose`),
conforme o Manual de Segurança do PIX. A assinatura e as chaves públicas
(`/qrc/jwks`) não mudam; se vocês já extraíam o campo `jws` do JSON, basta
passar a usar o corpo da resposta diretamente. Avisaremos quando publicar."

---

## Avaliação 100% do QR Code versus a referência AVIV (mandato)

**Aderente (sem ação):** EMV dinâmico com URL sem protocolo em 26/25, GUI
`br.gov.bcb.pix`, PoIM 12, `62/05 = ***`, CRC-16/CCITT-FALSE; txid estático 1-25
e dinâmico 26-35 alfanumérico; JWS com header mínimo do manual e JWKS público;
ciclo de vida com 410 para removida e 200 para paga (CONCLUIDA); CobV com
calculadora de juros/multa/desconto e claims completos (dataDeVencimento,
validadeAposVencimento, devedor, recebedor); suporte a `modalidadeAlteracao`
(AVIV nem tem); QR estático de valor em aberto no motor.

**Corrigido nesta sessão:** envelope JSON na payload URL (#111) e 406 para
`Accept: application/jose`.

**Itens 1-3 (AUTORIZADOS pelo dono em 16/07 e IMPLEMENTADOS na mesma sessão,
commits `aea3d7c0` pix + `2ee0ad71` core):**
1. `POST /pix/charges` agora DELEGA ao motor de QR dinâmico REAL da cabine
   (subjects novos `monetarie.pix.qrcode.dynamic`/`.static` no
   CoreGatewayHandler, mesma disciplina de wire do cobv): EMV com
   `location_url` em 26/25 + PoIM 12 e payload JWS servido em
   `GET /qr/v2/:token`. O EMV local de forma estática com valor rotulado
   "dynamic" foi REMOVIDO. Resposta ganha `locationUrl` (aditivo); espelho no
   Core preserva `GET /pix/charges/:id` e o webhook `pix.charge.created`.
   Prova: teste afere a chamada ao gateway (valor em reais string +
   expires_in) e o `locationUrl` na resposta; suite partner 45/0.
2. `POST /api/partner/v1/pix/qrcodes/static` criado: `amount` OPCIONAL
   (ausente = valor em aberto, SEM tag 54 no EMV; presente = valor fixo).
   Encoder canônico da cabine ganhou `:amount` opcional no estático (forma
   AVIV `maybe_add_amount`), provado por teste de decode (tag 54 ausente no
   aberto, "10.50" no fixo). Schemas OpenAPI + doc do portal (pt/en/es).
3. JWS do QR dinâmico: default RS512 -> **PS256** (RECOMENDADO pelo Manual
   §4.2). Prova empírica de interop: teste verifica a assinatura com
   `:public_key.verify` exigindo **saltlen ESTRITO de 32 bytes** (RFC 7518
   §3.5) — a forma que Itaú/Santander exigiram na experiência da AVIV. RS*
   segue aceito na verificação (allowlist inalterada); JWKS público não muda
   de forma (kty/key_ops/kid/x5t/x5c/n/e, alg não é anunciado por chave).

Suítes no fecho dos itens 1-3: settlement_service **962/0** (5 skipped
pré-existentes), shared crypto+pix+simulator **277/0**, spi nats **3/0**,
core partner_v1+spb+merchant portal **187/0**.

## Pendências desta frente

- Push + deploy dos 5 commits (pix-api e core-api; zero migrations): aguardam
  OK do dono. Ordem sugerida: pix-api ANTES do core-api (o Core novo chama os
  subjects novos da cabine; a cabine nova é retrocompatível com o Core velho).
- Validação viva pós-deploy: curl da payload URL (#111: corpo `eyJ...`,
  content-type `application/jose`, alg PS256); `ted.received` orgânico com os
  campos novos (#110); cobrança dinâmica nova do parceiro com `locationUrl`
  resolvível; QR estático em aberto lido por app pagador.
- Envio das respostas aos chamados (textos prontos acima; o do #109 já pode
  incluir o endpoint novo).
