Runbook: trocar de parceiro sem downtime¶
Para: Dev · Adm — Conferido em 2026-10-31.
O que é¶
Procedimento numerado para trocar de parceiro de liquidação sem parar o negócio. Serve para três casos:
- sair da MaximaxPay e entrar em outro PSP;
- adicionar um segundo provedor de depósito ao lado do atual (redundância e failover);
- reagir ao dia em que o parceiro rompe de repente (última seção).
A troca é possível porque o provedor é um processo à parte, plugado por registro, e o dinheiro em voo continua sendo tratado pelo provedor que o originou. Os estados e conceitos usados aqui estão em ciclo de vida do provedor.
Subcomandos do ops
Os comandos desta página são os do grupo dracma_ops provider: list, show, inflight, sync, register, certify, enable, drain, disable, unplug, replug e cutover (mais install). O nome do provedor é um argumento posicional e --root aponta a instância; nos exemplos, R=/dracma-hml em hml e R=/dracma em prd. Cada transição reaplica sozinha o ingress (rotas de callback) e o egress (hosts do descriptor) e, no unplug, para o adaptador. Toda operação no servidor passa pelo ops; nada é feito à mão com ssh, systemctl ou edição de arquivo.
Quando usar¶
| Situação | Use |
|---|---|
| O contrato com o parceiro termina em data conhecida. | Seções 1 a 12, com folga de semanas. |
| Quer redundância de depósito (segundo PSP). | Seções 1 a 6; pule 9 a 11 (o antigo continua). |
| Parceiro indisponível ou encerrado sem aviso. | Parceiro que rompe de repente. |
| Só testar um provedor novo em hml. | Seções 1 a 4. |
Como funciona¶
A troca avança em etapas com portões de go/no-go. Em cada etapa o provedor antigo continua enabled e atendendo, de modo que sempre existe um caminho de volta. Só se desliga o antigo depois de drenado.
Etapa 1. Pré-requisitos¶
Antes de qualquer comando:
- Contrato comercial com o novo parceiro assinado, credenciais de sandbox e de produção em mãos (as de produção ficam com quem cadastra a conexão no painel; nunca vão para repositório).
- Documentação do parceiro: autenticação, criação de cobrança PIX, consulta, saque (se for o caso), tarifa, assinatura de callback, IPs de origem dos callbacks e ambiente de teste.
- Decisão registrada (ADR) sobre o parceiro novo: motivo, direções (
cash_in,cash_out), data-alvo. - Janela combinada com quem opera o financeiro, e quem decide go/no-go identificado.
- Backup do estado atual: saída de
dracma_ops provider list --root $Re dedracma_ops provider inflight <antigo> --root $Rguardada no registro da mudança. - Alertas do provedor antigo funcionando (para ter linha de base antes de mexer).
- Servidor de hml com o mesmo desenho de prd (mesmo ops, mesmo executor).
Go/no-go da etapa: todos os itens acima cumpridos. Rollback: não há nada a desfazer.
Etapa 2. Preparação: adaptador e certificação em hml¶
2.1 Obter ou escrever o adaptador. Se já existe um adaptador SPP certificado para o parceiro (nosso ou de terceiro), use-o. Se não, escreva um dracma_psp_<nome> que fale o SPP v1: descriptor, saúde, cobrança, consulta, saque (se suportar), verificação de callback, idempotência e erros com effect correto.
Pontos que mais pesam na troca:
- Idempotência. Cobrança por
deposit_id, saque por chave de idempotência; repetir com a mesma chave não pode gerar segundo efeito no parceiro. - Consulta de saque por chave. É a fonte de verdade para resolver saque ambíguo. O adaptador precisa responder para qualquer chave aceita.
- Erro ambíguo é ambíguo. Timeout e 5xx do parceiro viram
effect: unknown. Nunca converta em falha definitiva. - Callback. Verificação sobre os bytes crus, em tempo constante. O callback sozinho nunca move dinheiro: o executor confirma por consulta.
- Ambiente. Declarar
environments.sandboxe a política de ambiente, para que fora de produção só se use sandbox.
2.2 Instalar o adaptador em hml e certificar.
O adaptador entra no ops por um PR no dracma_ops (bloco do serviço em services.yaml com provider: {name: novopsp}, porta, deploy e o pin do repo em release.yaml). Depois disso, o install o implanta como qualquer serviço (usuário próprio, unit hardened, par HMAC por arquivo) e o resto é com o provider:
R=/dracma-hml
dracma_ops install --env hml --root $R --only novopsp # só o adaptador novo; --only refaz apenas esses serviços
dracma_ops provider register novopsp --root $R
dracma_ops provider certify novopsp --root $R --report relatorio.json # ou rode o kit: --bin spp-conformance (--no-upstream, --fake-control-url …)
novopsp registrado (descriptor 7be1…44a0). Próximo: `provider certify novopsp` e `provider enable novopsp`
novopsp certificado: 58/58 casos (suite 1.0.0, descriptor 7be1…44a0)
O certify só grava a certificação se o descriptor_sha256 do relatório for o do registro. O par HMAC do kit vai por arquivo temporário 0600, nunca por argumento.
2.3 Rodar o fluxo de ponta a ponta em hml, com credenciais de sandbox: criar depósito, pagar no sandbox do parceiro, receber o callback, confirmar por consulta, fazer um saque (se houver) e consultar. Efeito externo real nunca sai de hml: e-mail e SMS do parceiro só com destinatário de teste.
Go/no-go: certificação completa (passou igual ao total) e fluxo de ponta a ponta verde em hml. Rollback: dracma_ops provider unplug novopsp --root $R em hml (sem em-voo, não precisa de --force).
Etapa 3. Instalar em paralelo em prd¶
A instalação em prd é a mesma de hml e roda em paralelo (número de jobs igual ao de cores, nproc por padrão):
dracma_ops install --env prd --root /dracma --only novopsp
O install cria, para o adaptador: usuário Linux próprio (dracma-novopsp), unit systemd hardened (spp-v1 §13), porta loopback do services.yaml, par INTERNAL_HMAC_NOVOPSP_EXECUTOR (gerado no seeder, entregue por arquivo ao adaptador e por env só ao executor) e o /health conferido. O egress (egress_hosts do descriptor) e as rotas de callback são aplicados pelos comandos provider register/enable, a partir do descriptor.
O provedor antigo não é tocado. Se a instalação falhar por colisão (porta, usuário, regra de firewall), pare: serviços que não instalam em paralelo não são independentes, e isso é prioridade máxima a corrigir. Não serialize para esconder o problema.
Go/no-go: unit ativa e saúde ok. Rollback: nada registrado ainda; basta não registrar (o adaptador parado fica sem egress).
Etapa 4. Registrar¶
R=/dracma
dracma_ops provider register novopsp --root $R
dracma_ops provider certify novopsp --root $R --report relatorio.json
dracma_ops provider list --root $R
A certificação de prd precisa ser do descriptor_sha256 do adaptador instalado em prd. Se o artefato é o mesmo de hml (mesmo commit), o hash coincide.
PROVEDOR ESTADO SAÚDE MODO EM-VOO(cob/saq) CERT DESCRIPTOR
maximaxpay enabled ok em-processo 41/6 97/97 a1b2c3d4e5f6
novopsp registered ok em-processo 0/0 58/58 7be1c0a944a0
CERT mostra passou/total só quando a certificação é do descriptor_sha256 atual (não caso contrário). MODO é remoto quando o executor fala SPP com o adaptador (veja corte para o adaptador).
Go/no-go: estado registered, saúde ok, CERT preenchido. Rollback: dracma_ops provider unplug novopsp --root $R (sem em-voo).
Etapa 5. Habilitar com tráfego dividido¶
dracma_ops provider enable novopsp --root $R
O enable repete a guarda de ambiente do executor: provedor sintético nunca em prd; provedor real fora de prd só com environment_policy.live_outside_prd: flag e o nome em PSP_REAL_OUTSIDE_PRD (dracma_ops secrets psp-real --env hml --enable --provider novopsp); certificação do descriptor atual. O ingress e o egress são aplicados antes da transição; se ela falha, o ops reaplica o estado real.
O novo provedor passa a enabled, mas ainda não recebe tráfego: não há conexões de tenants apontando para ele. Dois modos de dividir o tráfego, escolha conforme o caso.
| Modo | Como | Quando |
|---|---|---|
| Por conexão e prioridade | Criar a conexão do tenant no novo provedor com prioridade pior (número maior) que a do antigo; a menor prioridade ganha. O novo só recebe via failover. | Segundo provedor de depósito como redundância. |
| Por tenant e operação (canário) | Criar a conexão no novo provedor com prioridade melhor para um tenant piloto e uma direção. | Troca de parceiro. |
A prioridade vai de 1 a 1000 (menor ganha) e a direção de cada conexão é cash_in, cash_out ou both. Conexões novas são criadas no painel pelo tenant (credencial própria, validada pela checagem de credencial do provedor) ou por quem opera.
Para ter failover de depósito, o executor precisa estar com ele ligado (chave de configuração, desligada por padrão) e haver ao menos dois provedores de depósito habilitados.
Go/no-go: enabled, saúde ok, conexão piloto criada e credencial validada (valid: true). Rollback: dracma_ops provider disable novopsp --root $R (sem em-voo vai direto a disabled).
Etapa 6. Mover o roteamento em canário¶
Mova em degraus, sempre começando por depósito (cobrança não move dinheiro) e deixando saque por último.
| Degrau | Escopo | Tempo mínimo de observação | Operação |
|---|---|---|---|
| C0 | Um tenant interno ou piloto, cash_in |
24 h | Depósitos reais de valor baixo. |
| C1 | 5% dos tenants, cash_in |
24 h | Comparar taxa de sucesso e latência. |
| C2 | 25% dos tenants, cash_in |
48 h | Incluir pico de fim de semana. |
| C3 | 100% dos tenants, cash_in |
72 h | Antigo ainda enabled. |
| S0 | Um tenant piloto, cash_out, valor baixo |
48 h | Conferir saque até o estado terminal. |
| S1 | 25% e depois 100%, cash_out |
48 h cada | Só com S0 limpo. |
Cada degrau muda prioridades de conexões, nunca o núcleo. Saques e cobranças já em voo continuam com o provedor que os originou; só trabalho novo vai para o novo.
Go/no-go de cada degrau: critérios da etapa 7 satisfeitos pelo período mínimo. Rollback de cada degrau: devolver a prioridade melhor para a conexão do provedor antigo (o trabalho novo volta a ele); o que já saiu pelo novo continua sendo acompanhado pelo novo.
Etapa 7. Observar¶
Olhe os dois provedores lado a lado, pelo rótulo do provedor. Nomes reais do contrato:
| Sinal | Métrica ou alerta | O que esperar |
|---|---|---|
| Resultado por chamada | dracma_psp_requests_total por psp, method, op, outcome |
success dominante; business_rejected estável. |
| Latência | dracma_psp_request_duration_seconds por psp, method, op |
p95 igual ou melhor que o antigo. |
| Breaker | dracma_psp_circuit_state |
Fechado; qualquer abertura sustentada é motivo de recuo. |
| Failover | dracma_psp_failover (por motivo) |
Raro; pico indica instabilidade. |
| Saúde | dracma_spp_health por provider |
ok o tempo todo. |
| Estado do registro | dracma_spp_registry_state por provider e state |
enabled para o novo; draining só na etapa 9. |
| Em voo | dracma_spp_inflight por provider e kind |
Sobe no novo, desce no antigo. |
| Provedor | spp_upstream_requests_total, spp_upstream_duration_seconds, spp_idempotent_replays_total, spp_callbacks_verified_total |
Callbacks verificados com sucesso; replays idempotentes baixos. |
Os valores de outcome são: success, business_rejected, circuit_open, timeout, unavailable, unsupported, misconfigured, unauthenticated, unrecognized.
Alertas que devem estar ativos e silenciosos: provedor down, breaker aberto, resposta unrecognized_response, dreno parado. Observe também no painel: conexões em provider_unavailable e cobranças com needs_reconciliation.
Critérios de go/no-go para avançar um degrau:
| Critério | Go | No-go (recuar) |
|---|---|---|
Taxa de success do novo |
Igual ou melhor que a do antigo na mesma janela | Pior de forma sustentada |
timeout e unavailable |
Sem aumento sobre a linha de base | Aumento sustentado |
unrecognized |
Zero | Qualquer ocorrência sem explicação |
| Breaker do novo | Sempre fechado | Abriu e não se estabilizou |
| Callbacks | Todos verificados; nenhum perdido | unauthenticated inesperado ou callback sem efeito |
| Conciliação | Cobranças pagas batem com o extrato do parceiro | Divergência sem causa |
| Saques (S0 e S1) | Chegam a estado terminal; nenhum unknown preso |
unknown que não resolve por consulta |
needs_reconciliation |
Nenhum novo | Qualquer um novo |
Qualquer no-go: execute o rollback do degrau (etapa 6), registre a causa e só retome depois de corrigida.
Etapa 8. Mover o restante¶
Com C3 e S1 limpos, todo trabalho novo já está no provedor novo. Confira que as conexões de tenants novos nascem apontando para ele e que o provedor antigo deixou de receber trabalho novo.
Go/no-go: dracma_spp_inflight do antigo só decresce. Rollback: voltar prioridades para o antigo (ele continua enabled).
Etapa 9. Drenar o antigo¶
Só depois de uma janela estável (sugestão: uma semana inteira sem recuo):
dracma_ops provider drain maximaxpay --root $R
dracma_ops provider inflight maximaxpay --root $R
maximaxpay: drain → draining
cobranças pendentes: 4
saques não terminais: 2 (desconhecidos: 0)
Enquanto estiver em draining, o antigo não recebe trabalho novo, mas continua recebendo callbacks e sendo consultado. Acompanhe dracma_spp_inflight até zerar; o estado vira disabled sozinho e o evento provider.drained é emitido.
Go/no-go: em-voo decrescente. Rollback: dracma_ops provider enable maximaxpay --root $R (volta de draining ou disabled) e devolver as prioridades.
Etapa 10. Tratar saques em voo¶
Saque nunca troca de provedor. Para cada saque em voo no antigo:
| Estado do saque | O que fazer |
|---|---|
processing |
Esperar; o reconciliador consulta por chave. |
unknown |
Aguardar a consulta; se persistir, consultar o parceiro por canal próprio com a chave de idempotência. |
| Terminal (liquidado, falho ou devolvido) | Nada. Já saiu da contagem. |
| Preso além do prazo combinado | Decisão humana: confirmar com o parceiro e concluir manualmente. Nunca reenviar ao novo provedor. |
dracma_ops provider inflight maximaxpay --root $R
cobranças pendentes: 0
saques não terminais: 1 (desconhecidos: 1)
Go/no-go: contador zerado, ou saques restantes com decisão humana documentada. Rollback: voltar a enabled mantém a reconciliação normal.
Etapa 11. Desplugar¶
Com em-voo zero e o provedor em disabled:
dracma_ops provider disable maximaxpay --root $R # se ainda estiver em draining
dracma_ops provider unplug maximaxpay --root $R
maximaxpay: unplug → unplugged
O unplug remove as rotas de callback do ingress e o egress do usuário do adaptador (reaplicados pelo ops a partir do registro) e para a unit do adaptador (--keep-running a mantém). Registro, conexões e credenciais cifradas permanecem. Com trabalho ainda em voo, o ops recusa antes de chamar o executor, com o contador e a instrução de drenar. --force --reason "…" (motivo com 10 caracteres ou mais) só em break-glass, com motivo e decisão humana sobre cada saque (ver a última seção). Credenciais dos tenants permanecem cifradas; para encerrá-las de vez, peça a revogação ao parceiro e remova as conexões pelo painel.
Go/no-go: nenhum em-voo. Rollback: re-plugar com dracma_ops provider replug maximaxpay --root $R --enable (liga a unit, espera o /health, registra de novo e habilita se a certificação do descriptor_sha256 atual ainda vale; senão certify antes do enable), o que reativa as conexões provider_unavailable.
Etapa 12. Checklist final¶
- [ ] Novo provedor
enabled, saudável, certificado para odescriptor_sha256em uso. - [ ] Todo trabalho novo (depósito e saque) roteado para o novo.
- [ ] Antigo
unplugged, sem em-voo; registro e credenciais preservados. - [ ] Nenhum alerta aberto;
dracma_psp_circuit_statefechado. - [ ] Conciliação do período da troca bate com os extratos dos dois parceiros.
- [ ] Nenhum saque em
needs_reconciliationsem decisão. - [ ] Callbacks do novo parceiro configurados no painel dele (URL de callback e IPs de origem conferidos).
- [ ] Auditoria da mudança anexada: saídas de
listeinflight, certificação, decisões de go/no-go. - [ ] ADR atualizada e documentação interna revisada.
Rollback por etapa¶
| Etapa | Como desfazer |
|---|---|
| 2 | Desplugar em hml. |
| 3 | Nada registrado: não registrar. |
| 4 | dracma_ops provider unplug novopsp --root $R (sem em-voo). |
| 5 | dracma_ops provider disable novopsp --root $R. |
| 6 e 8 | Devolver prioridade melhor às conexões do antigo; o novo segue acompanhando o que já recebeu. |
| 9 | dracma_ops provider enable maximaxpay --root $R e devolver prioridades. |
| 10 | Voltar a enabled; a reconciliação normal continua. |
| 11 | Re-plugar o antigo e habilitar. |
Corte para o adaptador (cutover)¶
Enquanto o provedor ainda roda dentro do executor (adaptador em-processo), o corte para o adaptador SPP é um interruptor por provedor, reversível, que não muda comportamento de negócio:
R=/dracma-hml
dracma_ops provider cutover maximaxpay --to remote --root $R # EXECUTOR_PROVIDER_URLS=maximaxpay=http://127.0.0.1:<porta>/spp/v1
dracma_ops provider list --root $R # MODO passa a "remoto"
dracma_ops provider cutover maximaxpay --to in-process --root $R # volta
O cutover exige o /health do adaptador e o par HMAC no seeder; grava EXECUTOR_PROVIDER_URLS (nunca à mão), aplica o egress antes (o adaptador passa a alcançar o parceiro; o executor perde esse egress), refaz o deploy destrutivo só do executor (install --only executor) e ressincroniza. Se o redeploy falha, o ops restaura o seeder e a rede.
Parceiro que rompe de repente¶
O parceiro encerrou, foi suspenso, bloqueou a conta ou simplesmente está fora do ar. Nada disso exige deploy do núcleo, e o sistema já faz o seguinte sozinho: persiste, loga com trace_id, alerta, abre o breaker e (depósito, se ligado) faz failover. Saque nunca troca de provedor.
Primeiros 15 minutos¶
- Confirmar que não é falha nossa:
dracma_ops provider list --root $R(saúde do adaptador),dracma_ops logs <serviço>(unit ativa),dracma_ops provider sync --root $R(reaplica ingress/egress), e se o parceiro responde por canal independente. - Declarar incidente e abrir canal; registrar o horário.
- Parar o sangramento de depósito: se há outro provedor de depósito habilitado, o failover já desvia. Se o failover está desligado, mova prioridades das conexões para o provedor alternativo. Se não há alternativo, o depósito responde indisponível ao jogador: é o comportamento correto.
- Não reenviar saque. Saque em andamento fica
unknowne será consultado; reenviar pode pagar duas vezes. - Drenar para fechar a entrada de trabalho novo:
dracma_ops provider drain maximaxpay --root $R
dracma_ops provider inflight maximaxpay --root $R
- Fotografar o em-voo: guardar a saída de
inflighte a lista de saquesunknowncom horário.
Primeiras 24 horas¶
- Se o parceiro voltou: acompanhar o breaker fechar, a saúde reabilitar e o reconciliador resolver os
unknown; reabrir comdracma_ops provider enable maximaxpay --root $Rse foi drenado à toa. - Se o parceiro não volta: iniciar a troca de parceiro (etapas 1 a 6) com o plano de contingência mais rápido (canário comprimido, mas com os mesmos portões). Priorize depósito.
- Falar com o parceiro por todos os canais comerciais para obter: extrato dos saques do período, status de cada chave de idempotência e prazo de devolução de saldo retido.
- Comunicar tenants afetados (conexões ficam
provider_unavailablequando o provedor estádisabledouunplugged) com a estimativa de retomada ou a alternativa. - Revisar alertas: dreno parado, saques
unknownantigos,needs_reconciliation.
Saques desconhecidos¶
Saque unknown é aquele cujo resultado no parceiro não se sabe. Tratamento:
- O reconciliador consulta por chave de idempotência assim que o provedor responde. Se o parceiro só conhece o saque pelo painel, consulte lá.
- O resultado da consulta decide: liquidado, falho definitivo, devolvido ou ainda em processamento.
not_foundnão é definitivo por padrão (silêncio do parceiro não prova que não houve saque); só vale como definitivo se o descriptor do provedor declarar que o parceiro responde assim de forma conclusiva. - Se o parceiro encerrou e não responde: o saque fica
needs_reconciliation. Decisão humana, com evidência (extrato do banco, comprovante, confirmação do parceiro), e nunca reenvio automático. - Registre cada decisão com quem, quando e a evidência.
Conciliação¶
Depois da estabilização:
- Baixe os extratos do parceiro e do banco para o período da falha.
- Compare com os depósitos e saques do executor: pagos, expirados,
ambiguouseneeds_reconciliation. - Pagamentos que chegaram por tentativa antiga após failover (
ambiguous) são aceitos e marcadosneeds_reconciliation: decida cada um (creditar, estornar, aguardar). - Ajustes de ledger seguem o fluxo normal da área financeira; o provedor nunca altera saldo.
- Registre o resultado e anexe ao postmortem.
Se o parceiro encerrou de vez¶
- Drenar (já feito), tratar saques (acima) e desplugar com
--forcesomente se restar trabalho sem solução, com motivo auditado:
dracma_ops provider unplug maximaxpay --root $R --force --reason "parceiro encerrou operação; saques restantes em decisão humana; incidente 4471"
- Cobranças pendentes viram
expired; saques em voo ficamneeds_reconciliationcom o motivoprovider_unplugged. - Credenciais e conexões permanecem cifradas e preservadas; revogue o que puder no parceiro.
- Conclua a entrada do novo parceiro pelas etapas 1 a 12.
Erros comuns¶
| Erro | Efeito | Como evitar |
|---|---|---|
| Habilitar e achar que já há tráfego | Nada muda; o roteamento é por conexão. | Criar conexões e ajustar prioridades. |
| Mover saque junto com depósito | Risco de saque ambíguo em dois provedores. | Depósito primeiro, saque depois e em degraus. |
Reenviar saque unknown ao novo provedor |
Pagamento em duplicidade. | Nunca. Consultar por chave e decidir humanamente. |
| Desplugar o antigo cedo demais | Cobranças expiram e saques ficam em decisão manual. | Drenar e esperar o contador zerar. |
| Certificar em hml e habilitar em prd com adaptador diferente | O descriptor_sha256 não coincide e o enable é recusado. |
Promover o mesmo artefato do git. |
| Instalar em série para contornar colisão | Mascara falta de independência. | Corrigir a colisão. |
| Falha de failover por estar desligado | Depósitos falham em vez de trocar. | Ligar o failover antes da troca, com dois provedores habilitados. |
| Usar credencial de produção em hml | Efeito real fora de produção. | Sandbox em hml; a exceção exige a chave de política do ops e auditoria. |
Próximos passos¶
- Estados, drenagem e API de registro: ciclo de vida do provedor.
- Depois da troca, arquive o resultado (checklist da etapa 12) e atualize a documentação do parceiro novo.