Pular para o conteúdo principal
A sua instituição é um participante direto. Ela tem presença própria no sistema de liquidação nacional do Pix (SPI), e este plugin é o caminho dela até lá. Um participante indireto é uma instituição menor sem conexão própria. Ele chega ao SPI pela sua. Ele é cliente da sua instituição, não cliente do trilho. Essa relação existe no registro do seu tenant, não no BACEN. Você a cria com POST /v1/indirects. O modelo direto e indireto explica onde fica o dinheiro de um participante indireto: a conta de liquidação no Banco Central, o limite na JD e a posição no seu ledger.
Esta página explica o que uma participação hospedada é e como ela se comporta. Hospedar participantes indiretos trata do provisionamento: a chave de criptografia do segredo de entrega, a postura de hospedagem e a chamada de cadastro. A numeração dela continua a partir da página de configuração. Desses passos, apenas a chave de criptografia recusa todo cadastro sozinha, então provisione-a primeiro. A postura de hospedagem controla os caminhos do dinheiro, não o registro, então você pode cadastrar participantes antes de ativá-la.

Quem é responsável pelo quê


A maioria dos erros operacionais aqui vem de procurar a resposta na coluna errada. Na prática, um Pix para um cliente de um participante indireto para com você. Ele cai em uma conta que representa a posição daquela instituição. Você não sabe, e não pode saber, qual dos clientes dela é o dono do dinheiro. O participante indireto credita o cliente dele no próprio core, depois de receber o seu aviso.

O que cadastrar um indireto cria


Um cadastro grava uma linha, e o plugin também cria a posição de liquidação dessa instituição no Midaz. Essa é uma conta do ledger cujo alias começa com @pi_ e termina com o ISPB da instituição, o código de 8 letras maiúsculas ou dígitos que o BACEN dá a cada participante. Um indireto no ISPB 12345678 liquida em @pi_12345678. Essa posição guarda o dinheiro dessa instituição. A resposta a retorna como piAccountAlias. O plugin deriva o alias de ispb. Você nunca o escolhe, e um cliente nunca cria a conta. Cada instituição tem uma posição. Um crédito para um indireto cai apenas na conta desse indireto. Por isso o dinheiro de uma instituição nunca se mistura com o de outra, nem com o seu. O registro existe para dar essa segregação.
O alias usa underscore, não barra: @pi_12345678. O Midaz rejeita barra em um alias de usuário, então um nome no formato de caminho não resolve a nada.

Antes de cadastrar alguém


A participação indireta vem desligada por padrão. A flag do systemplane por tenant plugin-br-pix-jd.indirects/enabled deve estar ligada, e o valor padrão dela é false. A flag controla duas metades, e elas se comportam de formas diferentes:
  • A API de gerenciamento funciona com a flag desligada. Você pode cadastrar participantes, listá-los e ler um deles antes da habilitação. O onboarding antes do go-live é proposital.
  • Nenhum dinheiro se move com a flag desligada. A resolução decide a qual indireto um crédito recebido pertence. Com a flag desligada, ela reporta a funcionalidade como desabilitada. Nenhum crédito recebido vai para uma conta @pi, e nenhum indireto pode originar uma ordem de saída.
Duas operações recusam de imediato enquanto a flag está desligada, com 422 PIX-0111. Uma é a escrita de certificado de QR próprio em PATCH /v1/indirects/{indirectId}. A outra é GET /v1/indirects/{indirectId}/jwks.
A leitura falha fechada. Quando um caminho do dinheiro “volta ao comportamento de direto” e ninguém mudou a chave, essa é a causa. Olhe o systemplane.

Cadastrando um indireto


1

Ative a funcionalidade para o tenant

Defina plugin-br-pix-jd.indirects/enabled como true. O cadastro funciona sem isso, mas nada liquida até que esteja ligada.
2

POST /v1/indirects

Envie o name da instituição, o ispb dela, e um bloco delivery com endpointUrl e secret. A resposta é 201. Ela traz indirectId, o identificador pelo qual toda chamada posterior é roteada, e piAccountAlias.
3

Nada para consultar em loop: um 201 significa pronto

Toda checagem roda antes que o plugin grave a linha do registro. Primeiro vem a verificação de unicidade do ISPB, depois a verificação opcional no diretório da JD, depois a criação da conta @pi_{ispb} no Midaz. Apenas então o plugin grava a linha, com status ACTIVE. Não há estado intermediário nem nada a esperar.
4

Em caso de falha, corrija a causa e envie o POST de novo

Uma etapa que falha responde com um erro codificado que nomeia a etapa. O plugin não grava nenhuma linha do registro, então o ISPB continua livre. Corrija a causa e envie o POST de novo. Essa é a recuperação. Não há uma rota de nova tentativa separada, porque não resta nenhuma linha parcialmente escrita para retomar.
O cadastro é atômico: um 201 significa pronto, e uma falha não deixa nenhuma linha no registro. Em um 201, o status é sempre ACTIVE. Não escreva um cliente que fica consultando em loop até o participante ficar roteável. Esse loop espera por uma mudança que não pode acontecer. Uma falha pode deixar para trás apenas a conta de liquidação, quando essa etapa teve sucesso e uma posterior falhou. O plugin não a desfaz, e ela não causa dano. Nada é roteado para uma conta @pi sem uma linha ACTIVE no registro que aponte para ela. A criação da conta é idempotente por alias, então a sua próxima tentativa adota a mesma conta e não cria uma segunda. Uma falha nunca deixa uma linha no registro ou um ISPB retido. A resposta ainda traz um objeto provisioning com um campo failedStep. Ele é legado e sempre null para todo cadastro deste serviço, porque um cadastro malsucedido não deixa nenhuma linha. Os estados do ciclo de vida são ACTIVE, SUSPENDED e CLOSED.
PENDING_PROVISIONING é um estado aposentado com uma aresta afiada. Nenhum cadastro entra nele agora. GET /v1/indirects ainda o aceita como filtro, para que linhas de antes do cadastro atômico continuem sendo lidas como elas mesmas. Mas esse estado não tem transições de ciclo de vida em nenhuma direção. Você não pode suspender nem encerrar uma linha legada. Toda ação sobre ela responde 409 PIX-0094.Uma linha assim é legível, mas não roteável, e ainda mantém o ISPB dela contra o índice de unicidade aberto. Por isso ela bloqueia um novo cadastro dessa instituição, e nenhuma chamada de API consegue liberá-la. Limpar uma é uma decisão de operador e de dados, e não é uma operação de API. Se o seu tenant é mais antigo que o cadastro atômico, rode GET /v1/indirects?status=PENDING_PROVISIONING uma vez para encontrar essas linhas.

Como o dinheiro chega a um participante indireto


Acompanhe um Pix recebido. O indireto não chama nenhum endpoint nesse caminho. O seu plugin faz o trabalho.
  1. “Esta conta pode receber?” Antes de o Banco Central mover o dinheiro, ele pergunta se a conta de destino aceita o crédito. A pergunta chega até você, porque o participante indireto não tem linha com o SPI. Com o recurso ligado, o plugin responde pelo cliente de um indireto a partir do registro do indireto, e não a partir da conta. Ele lê o registro quando o seu catálogo não atende o ISPB do recebedor. Em uma devolução, ele o lê apenas quando o seu catálogo não atende nenhuma das partes, e consulta o ISPB do pagador quando o ISPB do recebedor não nomeia nenhum participante indireto.
    • ACTIVE é válido. CLOSED responde AC07, e qualquer outro estado responde AC06.
    • O plugin não verifica se a conta existe no core do indireto. Um crédito para uma conta que o indireto não tem cai na posição @pi dele.
    • Na validação síncrona, quando o plugin não consegue ler o registro ou a flag do recurso, ele responde 500.
  2. Um Pix chega para uma conta na instituição indireta. Ele alcança a sua participação direta pelo webhook de cash-in do plugin.
  3. A resolução encontra o indireto pelo ISPB no bloco do recebedor, no registro de participação. Há quatro desfechos, e apenas o primeiro credita um indireto:
    • O ISPB corresponde a uma participação ACTIVE. O crédito é lançado no alias @pi_{ispb} dessa participação.
    • O ISPB é o seu próprio. É o seu livro, e o CRM resolve a conta como de costume.
    • O ISPB não corresponde a nada, ou corresponde a uma participação suspensa ou encerrada. O plugin recusa o crédito com 404 PIX-0119 e não lança nada. A JD chama esta rota apenas depois que o SPI liquidou o crédito, então o dinheiro já está na sua conta de liquidação no Banco Central. Concilie esse endToEndId com o seu extrato do SPI.
    • A resolução falha por infraestrutura. O plugin nem credita nem recusa. Ele responde um erro, para que a JD entregue o crédito de novo. Ele falha fechado de propósito e não adivinha nada sobre dinheiro.
    O plugin nunca armazena em cache uma falha de resolução. Por isso um indireto que acabou de ficar ativo nunca fica escondido atrás de uma resposta negativa anterior.
  4. O crédito cai em @pi_{ispb}. O plugin registra a movimentação contra essa posição de liquidação e vincula seu próprio registro de transação ao indireto. Ele grava o aviso na mesma transação de banco de dados que o crédito. Ou os dois existem, ou nenhum existe.
  5. O plugin envia um aviso assinado ao delivery.endpointUrl que você cadastrou, com o payload da JD byte a byte. O plugin chama a instituição, não o contrário.
  6. O participante indireto credita o próprio cliente, no próprio core.
O plugin não consulta o CRM no caminho indireto. O destino vem do registro de participação, nunca dos dados do chamador. Isso é proposital. Se um participante indireto tivesse o seu próprio ISPB por engano, créditos para o seu próprio livro cairiam na conta dele. Por isso a verificação “esse ISPB é nosso?” roda antes da verificação “corresponde a um participante indireto?”.
O aviso carrega dois headers que a instituição receptora verifica: A entrega é pelo menos uma vez. O plugin pode enviar o mesmo aviso de novo após uma falha que permite nova tentativa, ou após uma queda entre o envio e o lançamento contábil. O endToEndId continua o mesmo entre as tentativas. Um receptor deve deduplicar pelo endToEndId e tratar um payload repetido como já processado.
O secret armazenado é apenas-escrita. Toda leitura de um indireto retorna delivery.secret como ***, então um secret perdido é substituído com um PATCH, nunca recuperado.

Quando o aviso nunca chega


O envio pode falhar de forma permanente. As tentativas se esgotam, ou o endpoint responde 4xx. Depois disso, o plugin marca o aviso como inválido e nunca mais o tenta. O dinheiro fica correto de qualquer forma. A movimentação é real, e a instituição não sabe dela. GET /v1/indirects/{indirectId}/transactions fecha essa lacuna. Ele retorna as movimentações que se liquidaram na conta @pi_{ispb} de um indireto. Por isso ele é o registro definitivo, não o aviso. Uma instituição que o lê em um agendamento não depende mais da entrega. Ele é também a única forma de ler de novo uma janela depois de uma indisponibilidade própria. Listar as transações de um participante indireto traz as regras que tornam uma conciliação correta. Elas cobrem a janela since semiaberta obrigatória, centavos inteiros, paginação por cursor do mais antigo para o mais novo e o limit limitado silenciosamente. Ler o feed não muda nada e não reenvia nenhum aviso.

Devolvendo um Pix que um participante indireto recebeu


A devolução de um Pix que caiu na posição de um participante indireto sai desse indireto. Envie POST /v1/refunds com o indirectId desse participante. A devolução então debita a posição @pi_{ispb} dele. Ela não precisa de accountId: quem devolve é o recebedor que o Pix original nomeia. Com um parceiro por trás da requisição, um accountId responde 400 PIX-0061. O indirectId deve nomear exatamente o participante que recebeu o Pix original. O plugin não move dinheiro, e responde:
  • 422 PIX-0135 quando o Pix original caiu na posição de um indireto, e a devolução não tem indirectId.
  • 404 PIX-0147 quando a devolução nomeia outro participante indireto, ou quando o Pix original caiu em uma conta direta e a devolução tem um indirectId.
O indirectId então passa pelas mesmas checagens de uma ordem de pagamento: 422 PIX-0111 quando a funcionalidade está desligada, 404 PIX-0095 para um participante desconhecido e 422 PIX-0100 quando a participação não está ACTIVE.

Hospedando o QR code sob o certificado próprio da instituição


Por padrão, o plugin publica o QR code dinâmico de um indireto sob a sua participação direta. Um indireto pode publicar o próprio em vez disso. Defina qrCertificate.ownCertificate com um publicBaseUrl em PATCH /v1/indirects/{indirectId}. A instituição então serve o documento assinado a partir do próprio host. A instituição não pode produzir as chaves de validação sozinha. A JDPI mantém o certificado e faz a assinatura. GET /v1/indirects/{indirectId}/jwks retorna o JWK Set desse indireto. A instituição o publica no próprio host, e PSPs pagadores validam contra ele. Busque-o quando configurar o certificado, e de novo após cada rotação. O status do ciclo de vida deliberadamente não controla essa leitura. Um indireto suspenso ou encerrado ainda tem QR codes ativos em circulação. Sem a chave, a validação deles quebraria.

Lendo o registro


Listar participantes indiretos traz as regras de paginação por cursor e o limit limitado silenciosamente. Um GET por id, e a lista, sempre mostram a verdade atual. Os caminhos do dinheiro leem uma projeção em cache. Cada réplica pode levar até o TTL do cache de resolução para ver uma suspensão. O TTL é de 30 segundos por padrão. O bloqueio do ledger vindo de suspend se aplica no momento da chamada.

Suspendendo, reativando e encerrando


PATCH /v1/indirects/{indirectId} carrega no máximo uma action de ciclo de vida: suspend, reactivate ou close. Cada uma escreve tanto no ledger quanto no registro.
  • suspend para tanto o envio quanto o recebimento na conta @pi do indireto. O próprio ledger recusa o dinheiro desse participante. Por isso uma suspensão vale mesmo em uma réplica cujo cache de resolução está desatualizado. reactivate retoma os dois.
  • close é permanente. CLOSED é terminal, e depois disso o plugin recusa também toda mudança de campo na linha.
Um encerramento não apaga nada. O plugin bloqueia a conta @pi de forma permanente, mas a mantém. Um cadastro posterior do mesmo ISPB reaproveita essa conta. A conta fica desbloqueada, ativa de novo, e mantém o histórico contábil. Um PATCH só com campos, sem action, muda esses campos. Um PATCH vazio não muda nada e retorna a linha atual. Isso não é um erro.
Não existe DELETE em /v1/indirects, e CLOSED é terminal. A única saída do registro é close, que mantém a linha e a conta. Um cadastro feito por engano em um tenant ativo permanece no registro desse tenant para sempre. O ISPB dele fica retido contra a restrição de unicidade aberta até você encerrá-lo. Todo código de instituição que você usa é permanente. Verifique name, ispb e o endpoint de entrega antes de chamar.

O que dá errado


As páginas de operação listam cada recusa desse registro, com o status e a condição exata. Elas também dizem o que o plugin gravou ou não gravou quando recusou. Veja Cadastrar um participante indireto, Atualizar um participante indireto, Consultar um participante indireto, Listar participantes indiretos, Consultar o JWK Set de um participante indireto e Listar as transações de um participante indireto. A Lista de erros do Pix JD traz cada código, com o detail exato de cada um.
O status diz se você deve repetir, não a família do código. PIX-0107 e PIX-0123 são a mesma condição, vista de duas formas. Um 409 significa que o trilho leu a configuração e constatou que a chave não existe. Apenas um operador pode mudar isso, então repetir entra em loop para sempre. Um 503 significa que a leitura não terminou. O trilho não sabe que a chave está ausente, então repetir é correto. Nenhum dos dois grava nada.

Para onde ir agora