R-009 — Importação dos pagamentos atrasados do checkout
TLDR: todo pagamento
overduedo checkout criado desde 01/01/2026 (meia-noite de Brasília) vira umDebitpendingcom todas as parcelas ativas. O atendente vem dodb/initial_import.csvou do round-robin, e o marcador de importação é(payment_provider, provider_payment_id). Cada produto do pagamento vira umProductDebit, com o progresso do aluno vindo do Apolo.
Specs: 20260928171636_import_overdue_checkout_payments.md · 20260928232447_import_checkout_products.md · 20260929010215_import_overdue_installments_only.md
Given / When / Then
Dado um pagamento do checkout com status = overdue, gateway_deleted falso e created_at a
partir de 01/01/2026 00:00 em Brasília,
Quando o ImportOverdueCheckoutPaymentsJob roda,
Então o nectar cria um Debit pending com as parcelas paid/pending/overdue do pagamento,
ligado ao cliente do mesmo CPF (criado se não existir) e ao atendente definido pela regra abaixo.
Rodar de novo não duplica nada.
Regras
| ID | Regra |
|---|---|
R-009.1 |
Fonte: payments.status = 'overdue', created_at >= since convertido para o início do dia em Brasília, COALESCE(payments.gateway_deleted, false) = false e ao menos 1 parcela overdue com COALESCE(installments.gateway_deleted, false) = false. Pagamento sem parcela atrasada ativa não é importado. Leitura paginada por keyset em payments.id |
R-009.2 |
payment_type vem de payments.kind: standard→installment, repayment→repayment_first, settlement→full_settlement, renewal→renewal |
R-009.3 |
payment_provider = payments.gateway em minúsculas. Fora de asaas/hotmart, o pagamento é pulado (unsupported_provider) |
R-009.4 |
Marcador de importação: provider_payment_id = payments.reference (o externalReference no Asaas), único por payment_provider. O que já foi importado é pulado (already_imported) |
R-009.5 |
provider_charge_id = payments.gateway_id, provider_installment_id = payments.gateway_installment_id, provider_checkout_url = payments.gateway_checkout_url, provider_customer_id = cus_ da organization do pagamento, organization_slug = organizations.slug |
R-009.6 |
Parcelas paid→paid, pending→upcoming, overdue→overdue. As demais não entram. provider_charge_id = installments.gateway_id, e payment_link e billing_type são copiados |
R-009.7 |
Cliente é buscado pelo CPF. Se existe, não é alterado, exceto preencher provider_customer_id vazio. Se não existe, é montado com os dados do checkout e validado antes do sorteio do atendente; o existente também é validado antes do sorteio quando recebe o provider_customer_id. Se for inválido, o pagamento é pulado (invalid_customer) sem consumir a vez de ninguém no round-robin. Pagamento sem cliente no checkout (missing_customer) ou sem CPF (missing_document) também é pulado |
R-009.8 |
Atendente: nome do CSV (primeira linha do CPF) entre os usuários ativos com role attendant ou admin (User.active_debit_assignees), ignorando caixa e acento. Senão, round-robin por id só entre os atendentes ativos (User.active_attendants), contínuo na execução. Admin recebe clientes só pelo CSV, nunca pelo rodízio. Na mesma execução, o mesmo CPF fica sempre com o mesmo atendente. O atendente atual do cliente não é considerado |
R-009.9 |
Cada pagamento roda na própria transação. Uma falha inesperada vai para failed e não interrompe o import |
R-009.10 |
Débito importado não se apaga (usar cancelled). Se for apagado, o próximo import traz o pagamento de novo |
R-009.11 |
Toda execução grava uma linha em checkout_import_runs, com as listas completas de pulados e falhas. finished_at nulo indica uma execução que não terminou |
R-009.12 |
Produtos: cada produto distinto dos checkouts do pagamento (payment_items + checkout principal, via checkouts.product_id) vira um ProductDebit, com external_id = products.slug (ou products.pid se o slug estiver vazio) e product_name = products.name. Checkout sem produto é ignorado |
R-009.13 |
Progresso: vem da matrícula do Apolo (GET /api/v2/students?email=) com courseSlug = external_id, preferindo a ativa e, entre elas, a mais recente. progressPercentage vai para consumer_progress (floor, 0..100), completedModules para watched_lessons (limitado ao total), totalModules para total_lessons, certificateIssuedAt presente para certificate_issued e expiresAt para expires_on (nulo = lifetime). Uma consulta por e-mail na execução. Sem matrícula ou com o Apolo fora do ar, o produto entra com os defaults e a falha conta em apolo_failures_count |
R-009.14 |
Reimport: débito já importado sem nenhum ProductDebit ganha os produtos (conta em products_backfilled_count) e continua em skipped como already_imported. Com algum produto, nada muda |