Assinaturas
Subscription (plano) vs CustomerSubscription (assinatura ativa) — billing intervals, trial, lifecycle e cancelamento.
A Zhex separa dois conceitos que muita plataforma confunde:
Subscription— o plano (template). DefinebillingInterval,trialPeriodDays, preço. Vive no produto.CustomerSubscription— a assinatura ativa de um cliente sobre aquele plano. Tem status, datas de billing, ciclo atual.
Pense em Subscription como menu, e CustomerSubscription como pedido em andamento.
Onde cada um é gerenciado
| Recurso | Configuração | Acesso |
|---|---|---|
Subscription (plano) | Dashboard → Produtos → Pagamento | Hoje só pelo dashboard. Endpoint público no roadmap. |
CustomerSubscription (instância) | API V1 — GET e cancel | /v1/customer_subscriptions/... |
A criação da CustomerSubscription é automática: quando o PaymentIntent em produto recorrente confirma com sucesso, a Zhex monta a assinatura usando o plano configurado no produto. Você não precisa de endpoint de "criar assinatura" — basta cobrar o produto.
Anatomia do plano (configurado no dashboard)
{
id: 'sub_abc',
productId: 'prd_xyz',
name: 'Premium Mensal',
billingInterval: 'MONTH', // DAY | WEEK | MONTH | YEAR
billingIntervalCount: 1, // a cada 1 mês
trialEnabled: true,
trialPeriodDays: 7,
isActive: true,
}A combinação billingInterval × billingIntervalCount cobre todos os casos:
| Cobrança | billingInterval | billingIntervalCount |
|---|---|---|
| Diária | DAY | 1 |
| Semanal | WEEK | 1 |
| Quinzenal | WEEK | 2 |
| Mensal | MONTH | 1 |
| Bimestral | MONTH | 2 |
| Anual | YEAR | 1 |
| A cada 2 anos | YEAR | 2 |
CustomerSubscription — anatomia
{
"id": "csub_abc",
"object": "customer_subscription",
"livemode": false,
"customer": "cus_…",
"subscription": "sub_…",
"payment_intent": "pi_…",
"status": "ACTIVE",
"current_period_start": 1714060000,
"current_period_end": 1716738400,
"next_billing_date": 1716738400,
"trial_start": null,
"trial_end": null,
"cancel_at": null,
"canceled_at": null,
"cancel_reason": null,
"current_billing_cycle": 3,
"created": 1707000000
}Lifecycle de CustomerSubscription
A assinatura ativa de um cliente percorre uma máquina de estados:
| Status | Significado | Ação |
|---|---|---|
INCOMPLETE | PaymentIntent da 1ª cobrança ainda não confirmou | Aguardar; libera quando vira ACTIVE |
TRIALING | Em período de trial gratuito | Conceder acesso; cobrança automática ao fim |
ACTIVE | Cobrando normalmente | Conceder acesso |
PAST_DUE | Última cobrança falhou; em retry | Manter acesso por 7d (grace period) |
UNPAID | Esgotou retries; aguardando pagamento manual | Bloquear acesso |
CANCELED | Encerrada (final) | Bloquear acesso, preservar histórico |
Listar assinaturas
// todas
for await (const sub of zhex.customerSubscriptions.list({ limit: 100 }).autoPagingEach()) {
/* … */
}
// filtrar por customer
for await (const sub of zhex.customerSubscriptions.list({
customer: 'cus_…',
status: 'ACTIVE',
limit: 100,
}).autoPagingEach()) {
/* … */
}customerSubscriptions no SDK — verificação
Hoje o SDK @zhexio/node cobre os recursos de cobrança direta (customers, paymentIntents, paymentMethods, refunds, tokens). Para customer_subscriptions use fetch direto enquanto adicionamos o resource ao SDK.
Cancelar uma assinatura
POST /v1/customer_subscriptions/:id/cancel — três modos:
curl -X POST https://prometheus.zhex.io/v1/customer_subscriptions/csub_…/cancel \
-H "Authorization: Bearer $ZHEX_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "reason": "requested_by_customer" }'
# Status vira CANCELED na hora.cancel_at_period_end e cancel_at são mutuamente exclusivos. reason é livre — vai no audit trail.
Trial
Trial é configurado no plano (dashboard). Quando o cliente assina um produto recorrente com trial:
CustomerSubscriptionnasce emTRIALINGcomtrial_start/trial_endpopulados.- Nenhuma cobrança acontece no período.
- No
trial_end, a Zhex dispara o primeiroPaymentIntentautomaticamente. Sucesso →ACTIVE. Falha →INCOMPLETE→ retry → eventualmenteUNPAID.
Liberação de acesso: trate TRIALING e ACTIVE como acesso liberado; PAST_DUE como grace period; UNPAID/CANCELED como bloqueado.
Webhooks
Hoje a API V1 emite os events de PaymentIntent que são side-effect das cobranças recorrentes:
payment_intent.succeeded— cobrança do ciclo N foi bem-sucedidapayment_intent.payment_failed— cobrança do ciclo N falhou (assinatura entra emPAST_DUE)payment_intent.canceled— primeira cobrança cancelada antes de virar ativa
Para reagir a transições da própria CustomerSubscription (activated, trial_will_end, canceled), consulte o estado via GET /v1/customer_subscriptions/:id no momento que precisar — o vocabulário de events específico de subscription está no roadmap (customer_subscription.*).
Boas práticas
statement_descriptorcurto e branded — aparece mensalmente no statement do cliente.trialPeriodDays: 7é o sweet spot. Menor que 3 não converte; maior que 14 aumenta churn de "esqueci de cancelar".- Trate falha de cobrança recorrente com email automático "atualize seu cartão" no
payment_intent.payment_failed. Sem isso, churn involuntário sobe. - Nunca delete
CustomerSubscription— sempre cancele. Histórico é receita LTV, financeiro e reconciliação.
Próximos passos
Cupons
Desconto fixo ou percentual em produtos e assinaturas
Webhooks
Tratar payment_intent.succeeded sem duplicar entrega
Conceitos
PaymentIntent, Token, Idempotência
Atualizado em