Garu

2026-05-05

v0.7.0 — Cobranças recorrentes com cartão e período de teste

scheduled-chargesrecorrentescartaotrialdashboardapisdkmcp

A próxima etapa das cobranças agendadas chegou: agora você pode cobrar mensalmente (ou em qualquer outra cadência) com cartão de crédito, período de teste, e ações de ciclo de vida que cabem em um SaaS — tudo integrado ao mesmo wizard que você já usa para cobranças avulsas.

O que mudou

Recorrência com cartão

Crie uma cobrança recorrente em uma de sete cadências (semanal, quinzenal, mensal, bimestral, trimestral, semestral ou anual). No primeiro ciclo, o cliente abre o link de pagamento e cadastra o cartão uma única vez — a Garu salva o token via Celcoin (sem guardar PAN nem CVV). Nos ciclos seguintes a cobrança roda automaticamente, sem e-mail, sem clique, e o cliente só recebe contato se o cartão falhar. Funcionou: webhook scheduled_charge.paid com o número do ciclo. Falhou: sistema de retry assume.

Retry + fallback automáticos

Se a cobrança silenciosa for negada, a Garu tenta novamente em até 4 cadências (+6h, +24h, +48h, mais o erro inicial), usando o endpoint nativo de retry do Celcoin. Se esgotar:

  • O cliente recebe o e-mail "Não conseguimos cobrar seu cartão" com link para PIX, Boleto ou troca de cartão.
  • O ciclo entra em overdue e o time recebe a sequência de dunning (D+1 / D+2 / D+3).
  • Webhook scheduled_charge.cycle_failed com fallbackEmailSent: true.

Período de teste (trial)

POST /api/scheduled-charges aceita trialDays (1 a 365). A Garu reagenda o ciclo 1 para hoje + N dias e dispara customer.trial_started na hora. No vencimento, se o pagamento converter, sai customer.trial_converted. Se passar 24h sem pagar, customer.trial_lapsed — sempre idempotente (o webhook só dispara uma vez por trial).

Ações de ciclo de vida (recorrente)

Cinco endpoints novos para administrar a série depois de criada:

  • POST /:id/cancel-recurrence — interrompe ciclos futuros. O ciclo em curso ainda pode ser pago; só depois disso a série vira recurrence_canceled. Final.
  • POST /:id/cancel-at-period-end { enabled: boolean } — soft-cancel estilo Stripe. Com true, depois do próximo ciclo pago a série encerra. Reversível.
  • POST /:id/payment-method { paymentMethodId } — troca o cartão salvo. Validação: o cartão precisa pertencer ao mesmo cliente.
  • DELETE /:id/payment-method — limpa o cartão salvo. Próximos ciclos voltam para o fluxo de e-mail-com-link.
  • POST /:id/mark-paid agora aceita cycleNumber opcional: para recorrentes é obrigatório, e marca apenas aquele ciclo como pago — futuros ciclos seguem normalmente.

Wizard "Nova cobrança"

A etapa Detalhes ganhou:

  • Toggle Avulsa / Recorrente.
  • Em recorrente: dropdown de cadência + campo opcional de período de teste (dias).
  • Checkbox Cartão habilitado quando o tipo é recorrente e existe um produto vinculado (cobranças com cartão exigem productId).
  • Validação inline para os casos de borda (cartão sem produto, trial em cobrança avulsa, etc).

API — request/response

POST /api/scheduled-charges agora aceita:

{
  "customerId": 42,
  "productId": 17,
  "amount": 49.9,
  "type": "recurring",
  "dueDate": "2026-06-01",
  "methods": ["card", "pix"],
  "recurrence": { "interval": "monthly" },
  "trialDays": 7
}

A resposta inclui trialEndsAt, paymentMethodId, cancelAtPeriodEnd, e o ciclo 1 já fica gravado em scheduled_charge_cycle com dueDate = today + trialDays.

Webhooks novos

Eventos da série:

  • scheduled_charge.recurrence_canceled
  • scheduled_charge.cancel_at_period_end_set / .cancel_at_period_end_cleared
  • scheduled_charge.payment_method_attached / .payment_method_changed / .payment_method_cleared
  • scheduled_charge.cycle_failed

Eventos de cliente (trial):

  • customer.trial_started / .trial_converted / .trial_lapsed

Todos os webhooks de eventos por ciclo carregam seriesId + cycleNumber no payload, para você rotear sem precisar de uma segunda chamada.

Migração

Se você já roda v0.6.0 com cobranças avulsas, nada quebra: a coluna trialEndsAt aceita null, paymentMethodId aceita null, e o cron de billing antigo continua funcionando. Recorrentes ficaram atrás de uma flag (seller_config.scheduled_charges_enabled) — se você quer testar, fala com a gente que liberamos.

Próximos passos

  • Cobranças com cartão em avulsa (hoje só recorrente).
  • Painel Em atraso com filtro por cliente para o time de cobrança.
  • Página pública do cliente para trocar o cartão sem precisar do link mágico.