Garu

2026-05-25

v0.12.0 — Cobrança resiliente: recuperação automática, skips visíveis e 'Cobrar agora'

scheduled-chargesrecorrentesdashboardapibillingreliability

Por que isso existe

Na v0.11.1 deixamos um aviso: "o cron usa filtro estrito due_date = today; cobranças que perderam o e-mail do dia precisam ser reagendadas manualmente". Na prática, isso significava que qualquer cobrança que não fosse processada exatamente no vencimento (e-mail do cliente ainda não cadastrado, ciclo recorrente gerado com data divergente, etc.) ficava órfã — presa em "Agendada", nunca mais reavaliada, e sem nenhum sinal visível. Esta versão fecha esse buraco.

Recuperação automática (com janela por cobrança)

O cron de billing agora varre due_date <= hoje (antes era = hoje). Uma cobrança que perdeu o dia é recuperada no próximo ciclo, em vez de ficar órfã.

Para não cobrar um cliente com semanas de atraso de surpresa, a recuperação respeita uma janela máxima:

  • Novo campo opcional maxRecoveryDays na criação da cobrança (POST /api/scheduled-charges). Cada cobrança escolhe sua tolerância — aperte para cobranças sensíveis a tempo, afrouxe quando cobrar atrasado é melhor que não cobrar.
  • Sem o campo, vale o padrão do sistema (SCHEDULED_CHARGE_MAX_RECOVERY_DAYS, hoje 14 dias).
  • Passou da janela? A cobrança não é disparada automaticamente — vira um evento billing_skipped (motivo stale) para revisão manual.

Dunning ancorado no disparo real

O relógio de atraso (avisos D+1 / D+2 / D+3 ao time) agora conta a partir de quando o cliente foi efetivamente cobrado, não da data nominal de vencimento. Para cobranças normais nada muda; para uma cobrança recuperada com atraso, isso evita o "tiroteio" de disparar os três estágios de dunning de uma vez.

Nenhum skip silencioso

Quando uma cobrança não pode ser disparada (sem e-mail do cliente, ou fora da janela), o billing agora registra um evento billing_skipped na timeline e alerta no Sentry — em vez de só logar e seguir. O motivo aparece direto na página da cobrança.

"Cobrar agora"

Novo botão no painel (e endpoint POST /api/scheduled-charges/:id/charge-now) que roda o mesmo disparo do cron — e-mail/notificação ao cliente + webhook + evento na timeline — imediatamente, sem esperar o vencimento. É idempotente: se a cobrança do ciclo atual já foi enviada, ele informa e não cobra de novo.

Adiar passou a valer para recorrentes

Em séries recorrentes, "Adiar" mexia só na data da série — que o cron de recorrência ignora, pois ele cobra os ciclos. Agora o adiamento também move o ciclo aberto, então a ação realmente surte efeito no billing.

Timeline completa

A linha do tempo da cobrança passou a rotular todos os eventos do backend em português (e-mail enviado, cobrança gerada, falha, avisos de atraso, ciclo de vida de cartão, trial, etc.) — antes vários apareciam como texto cru tipo email_sent_dday. O evento de e-mail mostra o destinatário (mascarado) e o ciclo.

Para desenvolvedores

  • POST /api/scheduled-charges/:id/charge-now — disparo manual. Wrappers de SDK + ferramenta MCP chegam em seguida nos repositórios públicos.
  • maxRecoveryDays agora é aceito em POST /api/scheduled-charges.