Integração de pagamento com plataformas externas (LMS)
TLDR: quando o status de um pagamento muda, o
PaymentListenerchama oPlatformService, que lêcheckout.integratione delega ao service da plataforma (Apolo, CBTRG ou Onion) para liberar ou bloquear o acesso do aluno.
Visão geral
O sistema usa um padrão event-driven (pub/sub via Wisper) para notificar plataformas externas. Cada plataforma recebe um webhook com os dados do pagamento e decide liberar ou bloquear o acesso.
A arquitetura tem seis camadas:
| Camada | Responsabilidade | Arquivos |
|---|---|---|
| Evento | Broadcast Wisper quando o pagamento muda de status | app/listeners/payment_listener.rb |
| Roteamento | Lê checkout.integration e direciona para o service correto |
app/services/platform_service.rb |
| Service | Decide a ação (grant/remove) a partir do status | app/services/checkout_integrations/lms/apolo/apolo_service.rb, app/services/cbtrg_service.rb, app/services/onion_service.rb |
| Módulo HTTP | Faz o POST para a API externa com autenticação |
app/models/apolo.rb, app/models/cbtrg.rb, app/models/onion.rb |
| Config | Define as plataformas disponíveis | config/initializers/platform_integrations.rb |
| Log | Registra request e response | app/models/webhook_log.rb |
| Retry | Job agendado para re-sync após 5 dias (overdue) | app/jobs/platform_run_integration_sync_job.rb |
Fluxo
mermaid
graph TD
A["Payments::Update<br/>broadcast payment_status_changed"] --> B["PaymentListener#payment_status_changed<br/>recebe payment + old_status"]
B --> C["PlatformService#process<br/>lê checkout.integration"]
C --> D["ApoloService"]
C --> E["CbtrgService"]
C --> F["OnionService"]
C --> G["process_whatsapp"]
C --> H["process_noop<br/>(compra avulsa)"]
D --> I["create_or_update(execute_now)"]
E --> I
F --> I
I --> J["Módulo HTTP<br/>POST na API externa"]
J --> K["WebhookLog"]
style C fill:#1f2937,color:#fff
style I fill:#374151,color:#fff
Decisão dentro de create_or_update
| Status do pagamento | Ação | Efeito colateral |
|---|---|---|
:paid |
grant_user_access |
Envia payload com status enabled/paid |
:overdue |
remove_user_access |
Agenda re-sync em 5 dias via PlatformRunIntegrationSyncJob.set(wait: 5.days) |
:refunded |
remove_user_access |
Bloqueio imediato, sem retry |
Resumido:
paid → grant_user_access → POST webhook (status: enabled/paid)
overdue → remove_user_access → POST webhook (status: disabled/overdue) + retry em 5 dias
refunded → remove_user_access → POST webhook (status: disabled/refunded)
Contratos
Apolo
POST {apolo_base_url}/checkout_notifications
Auth: token obtido por email/senha e cacheado no Redis
json
{
"course_ids": ["..."],
"status": "enabled",
"email": "...",
"name": "...",
"doc_number": "...",
"phone_number": "...",
"token": "...",
"transaction": "...",
"expires_at": "2025-12-31 23:59:59"
}
CBTRG
POST {cbtrg_base_url}/webhook?auth_token_verification={key}
Auth: token fixo em query param
json
{
"payment": {
"id": "...",
"status": "paid",
"billing_type": "BOLETO",
"checkout_id": 123,
"installment_count": 1,
"customer": {
"id": 1,
"email": "...",
"doc_number": "...",
"name": "...",
"phone_number": "..."
}
}
}
Onion
Contrato próprio, detalhado em onion_integration.md.
Registro no WebhookLog
Cada chamada grava dois registros — payload enviado e resposta recebida:
APOLO_WEBHOOK_CREATOR/APOLO_WEBHOOK_RESPONSECBTRG_WEBHOOK_CREATOR
Junto com gateway_customer_id, customer_email e payload.
Diferenças entre as plataformas
Autenticação
- Apolo — login com email/senha, token cacheado no Redis, refresh automático quando expira.
- CBTRG — token fixo enviado como query param (
auth_token_verification). - Onion — token fixo enviado no header
Authorization: Bearer.
Payload
- Apolo — envia
course_ids(IDs dos cursos no Apolo) eexpires_at, calculado a partir deactivation_total_months. - CBTRG — envia os dados completos do pagamento, incluindo
billing_type,installment_counte os dados do customer.
Tratamento de overdue
- Apolo — remove o acesso imediatamente e agenda re-sync em 5 dias.
- CBTRG — apenas agenda o re-sync em 5 dias; só remove o acesso quando
execute_now = true, depois que o job roda.
Repagamento
Quando o pagamento é um repagamento (original_payment presente):
- Usa a referência do pagamento original como ID da transação.
- Calcula a expiração a partir da data do primeiro pagamento da parcela original.
- Sincroniza com o mesmo curso/usuário na plataforma.
Referências
- payment_flow.md — o ciclo de vida completo que dispara este fluxo
- onion_integration.md — contrato da integração Onion
- ../../architecture/event_streaming.md — a arquitetura de eventos por trás dos broadcasts