Ciclo de vida completo de um pagamento

TLDR: um pagamento nasce draft na API, vira pending quando o Asaas o cria, e ao ser confirmado dispara dois broadcasts Wisper simultâneos — um para as plataformas externas (Apolo/CBTRG/Onion) e outro para os webhooks de integração das organizações.

Visão geral

O fluxo tem quatro etapas: recepção do pagamento, criação assíncrona no gateway, recepção do webhook de confirmação e notificação das duas famílias de consumidores externos.

mermaid graph TD A["POST /api/v2/payments"] --> B["Payment :draft"] B --> C["ScheduleWithGatewayJob"] C --> D["Asaas cria a cobrança<br/>Payment :pending"] D --> E["Cliente paga"] E --> F["POST /incoming_webhooks/.../asaas/payments"] F --> G["PaymentsJob → PaymentsFlow"] G --> H["Payments::Update<br/>Payment :paid"] H --> I["broadcast payment_status_changed"] H --> J["broadcast payment_processed"] I --> K["PlatformService<br/>Apolo · CBTRG · Onion"] J --> L["EventsHub<br/>IntegrationWebhooks"] style H fill:#1f2937,color:#fff style I fill:#374151,color:#fff style J fill:#374151,color:#fff

Fluxo

1. Recebendo o pagamento do usuário

Endpoint: POST /api/v2/payments Controller: app/controllers/api/v2/payments/payments_controller.rb

O controller recebe os dados (itens, cliente, forma de pagamento, afiliado, UTM) e delega para o orchestrator Payments::Creation::CreateFlow.

# Step O que faz
1 SetParamsInContext Normaliza os parâmetros de entrada
2 FindOrCreateCustomer Busca ou cria o customer no banco local
3 ValidateItems Valida se os itens do checkout existem
4 DealWithDuplicatedDraftPayment Evita pagamento duplicado
5 CalculatePaymentTotals Calcula totais, descontos, taxas
6 CreateLocalPayment Cria o Payment com status :draft
7 AttachAffiliate Associa afiliado, se houver
8 DispatchGatewayCreationJob Enfileira o job assíncrono para o Asaas

Neste ponto o pagamento existe localmente como :draft e o usuário já recebe resposta.

2. Enviando para o Asaas (assíncrono)

Job: Payments::Creation::ScheduleWithGatewayJob Flow: Payments::Creation::GatewayCreationFlow

# Step O que faz
1 FindById Busca o payment local
2 HaltIfNotInDraft Só processa se ainda for :draft
3 SetCustomer / SetOrganization Carrega o contexto
4 SetupApiClient Inicializa Asaas::Client com o token de gateway da organização
5 FindCustomerByEmail GET no Asaas — o cliente já existe?
6 CreateCustomer Se não existe, POST cria o cliente no Asaas
7 CreatePayment POST no Asaas; status vai para :creating_on_gateway e volta com gateway_id e gateway_checkout_url
8 Payments::Update Atualiza o payment local para :pending e emite payment_processed (PAYMENT_GATEWAY_CREATED)

O CreatePayment envia due_date, customer_id, external_reference, billing_type, installment_count, value e a configuração de split.

3. Asaas confirma: webhook de entrada

Endpoint: POST /api/v2/incoming_webhooks/payment_gateways/asaas/payments Controller: app/controllers/api/v2/incoming_webhooks/payment_gateways/asaas/payments_controller.rb

Recepção:

  1. check_security_token — valida o header asaas-access-token
  2. create_webhook — salva o payload em IncomingWebhookQueue
  3. halt_if_processed — evita reprocessamento
  4. Enfileira PaymentsJob

Processamento (PaymentsJob → PaymentsFlow):

# Step O que faz
1 ValidateParams Valida a estrutura do webhook Asaas
2 TranslateParams Normaliza os campos Asaas para o formato interno
3 FindByExternalReference Localiza o payment pelo reference
4 ProcessRefunds Processa os dados de reembolso
5 SetPaymentUpdateCondition Define se o payment deve ser atualizado
6 Installments::UpdateWithPaymentParams Atualiza a parcela
7 Payments::CalculatePaymentStatus Recalcula o status geral do pagamento
8 Payments::Update Atualiza o status local e dispara os broadcasts

Os steps 6 a 8 só rodam quando should_update_payment == true.

4. Notificando plataformas externas

Payments::Update é o ponto central: dispara dois broadcasts simultâneos, cada um com um destino diferente.

Caminho A — payment_status_changed → PlatformService. O PaymentListener#payment_status_changed chama PlatformService.new(payment.id, old_status).process, que lê checkout.integration e roteia para ApoloService, CbtrgService ou OnionService. Detalhado em payment_integration_flow.md.

Caminho B — payment_processed → EventsHub. O PaymentListener#payment_processed enfileira EventsHub::EventsDispatcher::Organization::RouteEventJob, que passa por RouteEventFlow → RouterSwitch → EventOutcomeWebhooksJob → OutcomeWebhooks::IntegrationWebhooks::Dispatcher::SenderFlow:

# Step O que faz
1 FetchListeners Busca IntegrationWebhook ativos cujos events contêm o evento
2 FilterListenersByCheckout Filtra webhooks por escopo de checkout
3 RescheduleDelayedListeners Lida com retries e atrasos configurados
4 BuildPayloads Monta o JSON via PaymentsPayload
5 SendWebhooks POST para cada webhook registrado

Cada envio é registrado em OutcomeWebhookLog com status, resposta e retries (3 tentativas, 5s entre cada).

Contratos

Mapeamento de status (Asaas → interno)

Evento Asaas Status interno
PAYMENT_CONFIRMED, PAYMENT_RECEIVED, PAYMENT_ANTICIPATED :paid
PAYMENT_PENDING, PAYMENT_CREATED, PAYMENT_AUTHORIZED :pending
PAYMENT_REFUNDED, PAYMENT_PARTIALLY_REFUNDED, PAYMENT_CHARGEBACK_REQUESTED :refunded
PAYMENT_OVERDUE :overdue
PAYMENT_DELETED :deleted_or_canceled_by_new_payment

Payload do Apolo

json { "course_ids": ["..."], "status": "enabled", "email": "...", "name": "...", "doc_number": "...", "phone_number": "...", "token": "...", "transaction": "payment_reference", "expires_at": "2025-12-31 23:59:59" }

Payload do CBTRG

json { "payment": { "id": "payment_reference", "status": "paid", "billing_type": "BOLETO", "checkout_id": 123, "installment_count": 1, "customer": { "id": 1, "email": "...", "doc_number": "...", "name": "...", "phone_number": "..." } } }

Payload dos webhooks de integração

json { "id": "webhook_<md5_hash>", "created_at": "2024-01-01T12:00:00Z", "version": "1.0.0", "event": "payments", "event_name": "PAYMENT_CONFIRMED", "data": { "payment": { "..." }, "current_installment": { "..." }, "original_payment": { "..." } } }

O contrato completo, com todos os eventos e campos, está em payment_webhooks.md.

Referências

Arquivo Responsabilidade
app/controllers/api/v2/payments/payments_controller.rb Endpoint de criação de pagamento
app/use_cases/payments/creation/create_flow.rb Orchestrator de criação local
app/use_cases/payments/creation/gateway_creation_flow.rb Orchestrator de criação no Asaas
app/services/asaas/client.rb Cliente HTTP do Asaas
app/controllers/api/v2/incoming_webhooks/payment_gateways/asaas/payments_controller.rb Recepção do webhook do Asaas
app/use_cases/incoming_webhooks/payment_gateways/asaas/payments/payments_flow.rb Processamento do webhook
app/use_cases/payments/update.rb Atualização de status e broadcasts
app/listeners/payment_listener.rb Listener Wisper de eventos de pagamento
app/services/platform_service.rb Roteamento para plataformas (Apolo/CBTRG/Onion)
app/services/checkout_integrations/lms/apolo/apolo_service.rb Integração Apolo
app/services/cbtrg_service.rb Integração CBTRG
app/services/onion_service.rb Integração Onion
app/use_cases/outcome_webhooks/integration_webhooks/dispatcher/sender_flow.rb Dispatcher de webhooks de saída
config/initializers/payment_statuses.rb Mapeamento de status
config/initializers/asaas_constants.rb Constantes de eventos Asaas

Documentos relacionados: payment_integration_flow.md, payment_webhooks.md, onion_integration.md, ../../architecture/event_streaming.md.