R-001 — Expiração de pagamento PIX e cancelamento de sessões

TLDR: Um pagamento PIX não confirmado em 30 minutos é expirado pelo sistema, que destrói permanentemente todas as sessões pending vinculadas àquela order.

Given / When / Then

Dado uma order com payment_method: pix e payment.status: pending Quando o Payments::ExpireJob roda 30 minutos após a criação do pagamento sem que o webhook de confirmação tenha chegado Então o pagamento passa a expired, as sessões são canceladas com o motivo “Sistema: Cancelada por falta de pagamento.” e as sessões pending são destruídas permanentemente

Tabela de decisão

Status do pagamento na execução do job Status das sessões Resultado
pending pending Pagamento → expired, sessões destruídas
pending scheduled Pagamento → expired, sessões canceladas (não destruídas)
paid / expired / qualquer outro qualquer Job sai cedo — nada muda

Restrições

  • A janela de expiração é de exatamente 30 minutos a partir da criação do pagamento (Payments::QueueJobs)
  • Payments::Expire só roda se payment.pending? — idempotente por construção
  • Sessões em pending são destruídas permanentemente (sem soft delete) via Meetings::DestroyAllByRelatedPayment
  • Sessões já scheduled são apenas canceladas, não destruídas
  • Se o webhook do gateway chegar depois da expiração confirmando o pagamento como paid, o sistema atualiza payment.status para paid mas não reativa nem recria as sessões — UpdateMeetingsByGatewayStatus protege com order.meetings.pending.exists?

Risco de condição de corrida

Existe uma janela crítica entre a criação do pagamento e a expiração em 30 minutos:

00:24 — Order + Payment criados (pending) 00:25 — Cliente paga o PIX 00:54 — ExpireJob roda → pagamento expirado, sessões destruídas ~depois — Webhook chega → pagamento atualizado para paid, mas não existe mais nenhuma sessão

Nesse cenário o pagamento fica paid no banco, sem nenhuma sessão ativa e sem repasse ao terapeuta — exigindo intervenção manual. O caso real está registrado em payments_pix_expiration_race_condition.

Teste vinculado

spec/use_cases/payments/expire_spec.rb — (sem teste ainda)