Ciclo de vida completo de um pagamento
TLDR: um pagamento nasce
draftna API, virapendingquando 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:
check_security_token— valida o headerasaas-access-tokencreate_webhook— salva o payload emIncomingWebhookQueuehalt_if_processed— evita reprocessamento- 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.