2026-05-05
v0.8.0 — Recorrência B2B2C: códigos de falha, vencimento de cartão e portal por produto
A v0.8.0 fecha as pontas do ciclo de vida de cobranças recorrentes que ficaram em aberto na v0.7.0: agora cada falha vem com um código identificável, cartões prestes a vencer avisam o seller antes de quebrar a cobrança, e plataformas B2B2C (SaaS que vende para sellers que vendem para clientes finais) podem customizar a página de pagamento por produto, não só por seller.
Toda transação ou ciclo recorrente que falha agora grava um GaruFailureCode estável, independente do gateway por baixo:
insufficient_funds, card_declined, card_expired, card_canceledprocessing_error, issuer_unavailable, fraud_suspected, invalid_cvvdo_not_honor_repeated, unknownO código é exposto em três campos: failureCode (enum normalizado), failureReason (mensagem em português) e gatewayFailureCode (código bruto do Celcoin, p.ex. ABECS 51/54/91). Você roteia em cima do enum, e ainda tem o código original para auditoria. Disponível no payload de transaction.payment.failed e em scheduled_charge.cycle_failed.
Antes, qualquer transação que não terminasse em paid virava transaction.failed. Agora a Garu separa por intenção:
transaction.payment.failed — tentativa de cobrança recusada (com failureCode).transaction.canceled — cancelamento explícito (pelo seller, por contrato, ou pela operadora).transaction.chargeback — chargeback registrado pelo emissor.Quem precisa rotear cancelamento, falha técnica e chargeback para fluxos diferentes não precisa mais inferir pelo motivo.
Cron diário às 09:00 BRT acompanha o ciclo de vida do cartão tokenizado:
payment_method.expiring_soon por estágio (sem disparar duas vezes o mesmo estágio).active → expired atomicamente, e sai payment_method.expired.expired, o ciclo é marcado failed com failureCode = 'card_expired' antes de tentar — não desperdiça uma chamada ao gateway.Isso resolve o cenário de SaaS com base recorrente grande: agora dá pra avisar o cliente antes da cobrança quebrar.
DELETE /api/scheduled-charges/:id/payment-method agora cancela qualquer retry em voo do ciclo atual: o cron de retry verifica que a série não tem mais cartão e zera o nextRetryAt em vez de tentar de novo. Race entre "trocar cartão" e "tentar de novo" fechada.
4 endpoints novos sob /api/products/:id/portal-config:
GET — retorna a config atual (ou null se o produto cai no fallback do seller).POST / PATCH — upsert com merge: só os campos enviados são gravados; os omitidos preservam o valor anterior. Mande null num campo para herdar do seller.DELETE — limpa a config inteira; o produto volta a usar a config do seller.Campos customizáveis: businessName, logoUrl, primaryColor, e a suíte completa de políticas do portal (allowCancelSubscription, requireCancelReason, cancelAtPeriodEndOnly, mensagens de boas-vindas / sucesso / cancelamento, e flags de e-mail).
Use case principal: plataformas que modelam professores / coaches / prestadores como Products sob um único Seller — a página de pagamento e o portal /minha-area podem ter logo, cor e nome de cada profissional, sem fragmentar a contabilidade do seller.
A v0.8.0 adiciona o serviço dispatchManualTestEvent com whitelist de eventos, payload de exemplo por tipo e merge raso de overrides. O endpoint HTTP que expõe esse serviço chega na v0.8.1 (POST /api/webhook-endpoints/:id/trigger) — o serviço por si só não é chamado por nada em v0.8.0.
@garuhq/node@0.7.0 traz os novos tipos e o recurso de portal por produto:
const cfg = await garu.products.portalConfig.set(57, {
businessName: 'Coach Maria — Corrida & Trilha',
primaryColor: '#257264',
logoUrl: 'https://cdn.exemplo.com/coaches/maria.png',
});
Tipos exportados: GaruFailureCode, FailurePayload, PaymentMethodExpiringPayload, PaymentMethodExpiredPayload, ProductPortalConfig, SetProductPortalConfigParams.
MCP server (@garuhq/mcp@0.7.0) ganhou 3 ferramentas novas: get_product_portal_config, set_product_portal_config, clear_product_portal_config. Agentes podem customizar o portal por produto direto pela ferramenta.
Aditivo. Nada quebra. O schema novo (colunas de failure_code/failure_reason/gateway_failure_code em transaction e scheduled_charge_cycle, tabela scheduled_charge_cycle_attempt, timestamps de expiring_*_notified_at / expired_at em payment_method, índice parcial em scheduled_charge.external_reference) é todo retrocompatível. Webhooks antigos (transaction.failed) continuam disparando para quem ainda assinou o legacy — adicione os novos eventos quando estiver pronto.
GET /api/scheduled-charges/:id/attempts) — granularidade de auditoria por tentativa.@garuhq/node para apps mobile.