Visao geral do fluxo
Pre-requisitos
- Node.js 18+
- Uma conta Chargefy com Organization Access Token
- SDK instalado:
@chargefy/sdk - Ambiente de sandbox configurado para testes
npm install @chargefy/sdk
pnpm add @chargefy/sdk
src/lib/chargefy.ts
import { Chargefy } from '@chargefy/sdk'
export const chargefy = new Chargefy({
accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
// Para sandbox:
// server: 'sandbox'
})
Passo 1: Criar produto com preco recorrente
O primeiro passo e criar um produto que tenha pelo menos um preco com tiporecurring. A Chargefy suporta as seguintes frequencias de cobranca:
| Frequencia | recurringInterval | Uso comum |
|---|---|---|
| Diaria | day | Trials, servicos por demanda |
| Semanal | week | Servicos semanais |
| Mensal | month | SaaS, streaming, planos gerais |
| Anual | year | Descontos para pagamento anual |
Via SDK
// Criar produto com precos mensal e anual
const product = await chargefy.products.create({
name: 'Plano Pro',
description: 'Acesso completo a todas as funcionalidades',
prices: [
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 9990, // R$ 99,90 (em centavos)
priceCurrency: 'brl',
recurringInterval: 'month',
},
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 99900, // R$ 999,00 (em centavos) — economia de ~17%
priceCurrency: 'brl',
recurringInterval: 'year',
},
],
})
console.log('Produto criado:', product.id)
console.log('Precos:', product.prices.map(p => ({
id: p.id,
interval: p.recurringInterval,
amount: `R$ ${(p.priceAmount / 100).toFixed(2)}`,
})))
Via cURL
curl -X POST https://api.chargefy.io/v1/products \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Plano Pro",
"description": "Acesso completo a todas as funcionalidades",
"prices": [
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 9990,
"priceCurrency": "brl",
"recurringInterval": "month"
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 99900,
"priceCurrency": "brl",
"recurringInterval": "year"
}
]
}'
Crie multiplos precos para o mesmo produto (mensal + anual) e ofereça desconto no plano anual para incentivar compromissos mais longos.
Criar produto com periodo de trial
const productComTrial = await chargefy.products.create({
name: 'Plano Starter',
description: 'Comece gratuitamente por 14 dias',
prices: [
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 4990, // R$ 49,90
priceCurrency: 'brl',
recurringInterval: 'month',
trialPeriodDays: 14,
},
],
})
Passo 2: Criar checkout para produto de assinatura
Quando o cliente escolhe um plano, crie uma sessao de checkout usando oproductPriceId do preco recorrente.
Via SDK
const checkout = await chargefy.checkouts.create({
productPriceId: 'price_mensal_xxxx', // ID do preco recorrente
customerEmail: 'cliente@empresa.com',
successUrl: 'https://meuapp.com.br/assinatura/sucesso',
metadata: {
userId: 'user_123',
plan: 'pro',
},
})
console.log('URL do checkout:', checkout.url)
console.log('Client secret:', checkout.clientSecret)
console.log('Expira em:', checkout.expiresAt)
Via cURL
curl -X POST https://api.chargefy.io/v1/checkouts \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"productPriceId": "price_mensal_xxxx",
"customerEmail": "cliente@empresa.com",
"successUrl": "https://meuapp.com.br/assinatura/sucesso",
"metadata": {
"userId": "user_123",
"plan": "pro"
}
}'
A Chargefy detecta automaticamente que o preco e recorrente e configura o checkout para criar uma assinatura apos o pagamento.
Passo 3: Processar primeiro pagamento
A primeira cobranca da assinatura e processada no checkout. O cliente pode pagar via PIX ou Cartao de Credito.Boleto nao e recomendado para assinaturas. A compensacao lenta (1-3 dias uteis) e a baixa taxa de conversao tornam o boleto inadequado para cobranças recorrentes. Use PIX ou Cartao de Credito.
Pagamento via PIX
const result = await chargefy.checkouts.confirm(checkout.clientSecret, {
customerEmail: 'cliente@empresa.com',
customerName: 'Maria Silva',
paymentMethod: 'pix',
})
// Exibir QR Code para o cliente
console.log('QR Code (copia e cola):', result.paymentDetails?.pixCopyPaste)
console.log('QR Code (base64 img):', result.paymentDetails?.pixQrCodeBase64)
Pagamento via Cartao de Credito
const result = await chargefy.checkouts.confirm(checkout.clientSecret, {
customerEmail: 'cliente@empresa.com',
customerName: 'Maria Silva',
paymentMethod: 'credit_card',
card: {
cardNumber: '4111111111111111',
holderName: 'MARIA SILVA',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
},
})
console.log('Status:', result.status) // 'succeeded'
console.log('ID do checkout:', result.id)
Em producao, nunca trafegue dados de cartao pelo seu servidor sem certificacao PCI-DSS. Use o Checkout Embed ou a tokenizacao client-side via Chargefy.js para enviar apenas o
card_id tokenizado.Passo 4: Plano de recorrencia (automatico)
Apos o primeiro pagamento ser confirmado, a Chargefy automaticamente:- Cria um plano de recorrencia com a frequencia e valor definidos no preco
- Vincula o metodo de pagamento do cliente (cartao tokenizado ou conta PIX) ao plano
- Agenda as cobranças futuras de acordo com o intervalo configurado
- Emite o webhook
subscription.createdseguido desubscription.active
Primeiro pagamento confirmado
|
v
Chargefy cria plano de recorrencia
|
v
Cobranças automaticas agendadas
|
v
A cada ciclo: cobranca automatica → Chargefy processa → Webhook para seu app
Para assinaturas com PIX, a Chargefy gera automaticamente um novo QR Code a cada ciclo e envia por email ao cliente. Para cartao de credito, a cobranca e processada automaticamente usando o cartao tokenizado.
Gerenciamento de assinaturas
Listar assinaturas de um cliente
const subscriptions = await chargefy.subscriptions.list({
customerId: 'cus_xxxx',
active: true,
page: 1,
limit: 10,
})
for (const sub of subscriptions.items) {
console.log(`${sub.id} | ${sub.status} | ${sub.product.name}`)
console.log(` Proximo ciclo: ${sub.currentPeriodEnd}`)
console.log(` Valor: R$ ${(sub.amount / 100).toFixed(2)}/${sub.recurringInterval}`)
}
curl -X GET "https://api.chargefy.io/v1/subscriptions?customerId=cus_xxxx&active=true" \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN"
Obter detalhes de uma assinatura
const subscription = await chargefy.subscriptions.get('sub_xxxx')
console.log({
id: subscription.id,
status: subscription.status,
productName: subscription.product.name,
priceName: subscription.price.id,
amount: `R$ ${(subscription.amount / 100).toFixed(2)}`,
interval: subscription.recurringInterval,
currentPeriodStart: subscription.currentPeriodStart,
currentPeriodEnd: subscription.currentPeriodEnd,
cancelAtPeriodEnd: subscription.cancelAtPeriodEnd,
customer: subscription.customer.email,
createdAt: subscription.createdAt,
})
Upgrade de plano (com prorata)
Quando um cliente faz upgrade para um plano superior, a Chargefy calcula automaticamente o valor proporcional (prorata) restante do periodo atual e cobra a diferenca.// Upgrade de Plano Starter (R$ 49,90/mes) para Plano Pro (R$ 99,90/mes)
const upgraded = await chargefy.subscriptions.update('sub_xxxx', {
productPriceId: 'price_pro_mensal', // novo preco
proration: true, // cobrar diferenca proporcional
})
console.log('Upgrade realizado:', upgraded.id)
console.log('Novo valor:', `R$ ${(upgraded.amount / 100).toFixed(2)}`)
console.log('Credito prorata:', `R$ ${(upgraded.prorationCredit / 100).toFixed(2)}`)
console.log('Cobranca adicional:', `R$ ${(upgraded.prorationAmount / 100).toFixed(2)}`)
curl -X PATCH https://api.chargefy.io/v1/subscriptions/sub_xxxx \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"productPriceId": "price_pro_mensal",
"proration": true
}'
Downgrade de plano
No downgrade, o novo preco entra em vigor no proximo ciclo de cobranca, sem cobranca adicional no periodo atual.// Downgrade de Plano Pro para Plano Starter no proximo ciclo
const downgraded = await chargefy.subscriptions.update('sub_xxxx', {
productPriceId: 'price_starter_mensal',
proration: false, // aplica no proximo ciclo
})
console.log('Downgrade agendado:', downgraded.id)
console.log('Plano atual ate:', downgraded.currentPeriodEnd)
console.log('Novo plano a partir de:', downgraded.scheduledChange?.effectiveDate)
Suspender assinatura
A suspensao interrompe temporariamente as cobranças sem cancelar a assinatura. Util para clientes com pagamento falho ou que solicitam uma pausa.const suspended = await chargefy.subscriptions.suspend('sub_xxxx', {
reason: 'Solicitacao do cliente — viagem',
})
console.log('Assinatura suspensa:', suspended.status) // 'suspended'
curl -X POST https://api.chargefy.io/v1/subscriptions/sub_xxxx/suspend \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Solicitacao do cliente" }'
Reativar assinatura suspensa
const reactivated = await chargefy.subscriptions.reactivate('sub_xxxx')
console.log('Assinatura reativada:', reactivated.status) // 'active'
console.log('Proxima cobranca:', reactivated.currentPeriodEnd)
curl -X POST https://api.chargefy.io/v1/subscriptions/sub_xxxx/reactivate \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN"
Cancelar assinatura
A Chargefy suporta dois modos de cancelamento:Cancelamento ao final do periodo (recomendado)
O cliente mantem acesso ate o fim do periodo ja pago. E o padrao mais justo e reduz chargebacks.const canceled = await chargefy.subscriptions.cancel('sub_xxxx', {
cancelAtPeriodEnd: true, // padrao
})
console.log('Cancelamento agendado')
console.log('Acesso ate:', canceled.currentPeriodEnd)
console.log('Status:', canceled.status) // ainda 'active'
console.log('Cancel at period end:', canceled.cancelAtPeriodEnd) // true
curl -X POST https://api.chargefy.io/v1/subscriptions/sub_xxxx/cancel \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "cancelAtPeriodEnd": true }'
Cancelamento imediato
O acesso e revogado imediatamente. Use apenas em casos de fraude, violacao de termos ou solicitacao explicita.const canceled = await chargefy.subscriptions.cancel('sub_xxxx', {
cancelAtPeriodEnd: false, // cancelamento imediato
reason: 'Violacao dos termos de uso',
})
console.log('Cancelamento imediato')
console.log('Status:', canceled.status) // 'canceled'
Reverter cancelamento agendado
Se o cliente mudar de ideia antes do fim do periodo:const reactivated = await chargefy.subscriptions.update('sub_xxxx', {
cancelAtPeriodEnd: false,
})
console.log('Cancelamento revertido:', reactivated.cancelAtPeriodEnd) // false
Webhooks de assinatura
Configure um endpoint de webhooks para reagir aos eventos do ciclo de vida da assinatura. Veja o guia de webhooks para setup completo.Eventos disponíveis
| Evento | Quando ocorre |
|---|---|
subscription.created | Assinatura criada apos primeiro pagamento |
subscription.active | Assinatura ativada (primeiro pagamento confirmado) |
subscription.updated | Alteracao de plano, preco ou metadados |
subscription.canceled | Assinatura cancelada (imediato ou ao fim do periodo) |
subscription.revoked | Acesso revogado (apos fim do periodo de assinatura cancelada) |
Implementacao do handler
src/routes/webhooks.ts
import { Router, raw } from 'express'
import crypto from 'crypto'
export const webhooksRouter = Router()
webhooksRouter.use(raw({ type: 'application/json' }))
function verifySignature(payload: Buffer, signature: string, secret: string): boolean {
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}
webhooksRouter.post('/', (req, res) => {
const signature = req.headers['webhook-signature'] as string
const secret = process.env.CHARGEFY_WEBHOOK_SECRET!
if (!signature || !verifySignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Assinatura invalida' })
}
const event = JSON.parse(req.body.toString())
// Responder imediatamente — processar async
res.status(200).json({ received: true })
// Processar evento
handleSubscriptionEvent(event).catch(err => {
console.error('Erro ao processar evento:', err)
})
})
async function handleSubscriptionEvent(event: any) {
const { type, data } = event
switch (type) {
case 'subscription.created':
// Registrar assinatura no banco de dados
await db.subscriptions.create({
chargefy_id: data.id,
customer_id: data.customer.id,
product_id: data.product.id,
status: 'pending',
amount: data.amount,
interval: data.recurringInterval,
current_period_end: data.currentPeriodEnd,
})
console.log(`Assinatura ${data.id} criada para ${data.customer.email}`)
break
case 'subscription.active':
// Liberar acesso ao servico
await db.subscriptions.update(
{ chargefy_id: data.id },
{ status: 'active' }
)
await grantAccess(data.customer.id, data.product.id)
await sendEmail(data.customer.email, 'welcome_subscription', {
productName: data.product.name,
amount: `R$ ${(data.amount / 100).toFixed(2)}`,
nextBilling: data.currentPeriodEnd,
})
console.log(`Assinatura ${data.id} ativada`)
break
case 'subscription.updated':
// Atualizar dados da assinatura (upgrade/downgrade)
await db.subscriptions.update(
{ chargefy_id: data.id },
{
amount: data.amount,
interval: data.recurringInterval,
product_id: data.product.id,
current_period_end: data.currentPeriodEnd,
}
)
console.log(`Assinatura ${data.id} atualizada — novo valor: R$ ${(data.amount / 100).toFixed(2)}`)
break
case 'subscription.canceled':
// Agendar revogacao de acesso
await db.subscriptions.update(
{ chargefy_id: data.id },
{
status: 'canceled',
cancel_at: data.cancelAtPeriodEnd ? data.currentPeriodEnd : new Date(),
}
)
if (data.cancelAtPeriodEnd) {
await sendEmail(data.customer.email, 'subscription_cancel_scheduled', {
accessUntil: data.currentPeriodEnd,
})
}
console.log(`Assinatura ${data.id} cancelada`)
break
case 'subscription.revoked':
// Revogar acesso definitivamente
await db.subscriptions.update(
{ chargefy_id: data.id },
{ status: 'revoked' }
)
await revokeAccess(data.customer.id, data.product.id)
await sendEmail(data.customer.email, 'subscription_revoked', {
productName: data.product.name,
})
console.log(`Assinatura ${data.id} revogada — acesso removido`)
break
}
}
Gerenciamento de faturas
Cada cobranca recorrente gera uma fatura (invoice). Voce pode consultar o historico de faturas do cliente.Listar faturas de uma assinatura
const invoices = await chargefy.subscriptions.listInvoices('sub_xxxx', {
page: 1,
limit: 12,
})
for (const invoice of invoices.items) {
console.log(`${invoice.id} | ${invoice.status} | R$ ${(invoice.amount / 100).toFixed(2)}`)
console.log(` Periodo: ${invoice.periodStart} — ${invoice.periodEnd}`)
console.log(` Pago em: ${invoice.paidAt || 'pendente'}`)
}
curl -X GET "https://api.chargefy.io/v1/subscriptions/sub_xxxx/invoices?page=1&limit=12" \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN"
Consultar fatura especifica
const invoice = await chargefy.invoices.get('inv_xxxx')
console.log({
id: invoice.id,
status: invoice.status, // 'paid', 'pending', 'overdue', 'void'
amount: `R$ ${(invoice.amount / 100).toFixed(2)}`,
paymentMethod: invoice.paymentMethod,
periodStart: invoice.periodStart,
periodEnd: invoice.periodEnd,
paidAt: invoice.paidAt,
invoiceUrl: invoice.url, // URL para visualizar a fatura
})
Exemplo completo: Sistema SaaS de assinaturas
Um exemplo funcional com backend Express e dashboard React.Backend (Express)
src/routes/subscriptions.ts
import { Router } from 'express'
import { chargefy } from '../lib/chargefy.js'
export const subscriptionsRouter = Router()
// Listar planos disponiveis
subscriptionsRouter.get('/plans', async (_req, res) => {
try {
const products = await chargefy.products.list({
isArchived: false,
isRecurring: true,
})
const plans = products.items.map(product => ({
id: product.id,
name: product.name,
description: product.description,
prices: product.prices
.filter(p => p.type === 'recurring')
.map(p => ({
id: p.id,
amount: p.priceAmount,
formatted: `R$ ${(p.priceAmount / 100).toFixed(2)}`,
interval: p.recurringInterval,
trialDays: p.trialPeriodDays || 0,
})),
}))
res.json(plans)
} catch (error) {
res.status(500).json({ error: 'Falha ao listar planos' })
}
})
// Criar checkout de assinatura
subscriptionsRouter.post('/subscribe', async (req, res) => {
try {
const { priceId, customerEmail } = req.body
const checkout = await chargefy.checkouts.create({
productPriceId: priceId,
customerEmail,
successUrl: `${process.env.FRONTEND_URL}/dashboard/assinatura/sucesso`,
metadata: { source: 'pricing_page' },
})
res.json({ checkoutUrl: checkout.url, clientSecret: checkout.clientSecret })
} catch (error) {
res.status(400).json({ error: 'Falha ao criar checkout' })
}
})
// Obter assinatura ativa do usuario
subscriptionsRouter.get('/me', async (req, res) => {
try {
const customerId = req.user.chargefyCustomerId // do seu middleware de auth
const subscriptions = await chargefy.subscriptions.list({
customerId,
active: true,
limit: 1,
})
if (subscriptions.items.length === 0) {
return res.json({ subscription: null })
}
const sub = subscriptions.items[0]
res.json({
subscription: {
id: sub.id,
status: sub.status,
plan: sub.product.name,
amount: `R$ ${(sub.amount / 100).toFixed(2)}`,
interval: sub.recurringInterval,
currentPeriodEnd: sub.currentPeriodEnd,
cancelAtPeriodEnd: sub.cancelAtPeriodEnd,
},
})
} catch (error) {
res.status(500).json({ error: 'Falha ao buscar assinatura' })
}
})
// Trocar plano (upgrade/downgrade)
subscriptionsRouter.post('/:id/change-plan', async (req, res) => {
try {
const { id } = req.params
const { newPriceId } = req.body
// Buscar assinatura atual para determinar se e upgrade ou downgrade
const current = await chargefy.subscriptions.get(id)
const newPrice = await chargefy.prices.get(newPriceId)
const isUpgrade = newPrice.priceAmount > current.amount
const updated = await chargefy.subscriptions.update(id, {
productPriceId: newPriceId,
proration: isUpgrade, // prorata apenas no upgrade
})
res.json({
subscription: updated,
changeType: isUpgrade ? 'upgrade' : 'downgrade',
effectiveDate: isUpgrade ? 'imediato' : updated.currentPeriodEnd,
})
} catch (error) {
res.status(400).json({ error: 'Falha ao trocar plano' })
}
})
// Cancelar assinatura
subscriptionsRouter.post('/:id/cancel', async (req, res) => {
try {
const { id } = req.params
const { immediate } = req.body
const canceled = await chargefy.subscriptions.cancel(id, {
cancelAtPeriodEnd: !immediate,
})
res.json({
status: canceled.status,
cancelAtPeriodEnd: canceled.cancelAtPeriodEnd,
accessUntil: canceled.cancelAtPeriodEnd ? canceled.currentPeriodEnd : null,
})
} catch (error) {
res.status(400).json({ error: 'Falha ao cancelar assinatura' })
}
})
// Reverter cancelamento
subscriptionsRouter.post('/:id/reactivate', async (req, res) => {
try {
const { id } = req.params
const reactivated = await chargefy.subscriptions.update(id, {
cancelAtPeriodEnd: false,
})
res.json({
status: reactivated.status,
cancelAtPeriodEnd: reactivated.cancelAtPeriodEnd,
})
} catch (error) {
res.status(400).json({ error: 'Falha ao reativar assinatura' })
}
})
// Historico de faturas
subscriptionsRouter.get('/:id/invoices', async (req, res) => {
try {
const { id } = req.params
const invoices = await chargefy.subscriptions.listInvoices(id, {
page: 1,
limit: 12,
})
res.json(invoices)
} catch (error) {
res.status(500).json({ error: 'Falha ao listar faturas' })
}
})
Dashboard React
src/components/SubscriptionDashboard.tsx
import { useState, useEffect } from 'react'
interface Subscription {
id: string
status: string
plan: string
amount: string
interval: string
currentPeriodEnd: string
cancelAtPeriodEnd: boolean
}
interface Plan {
id: string
name: string
prices: { id: string; amount: number; formatted: string; interval: string }[]
}
export function SubscriptionDashboard() {
const [subscription, setSubscription] = useState<Subscription | null>(null)
const [plans, setPlans] = useState<Plan[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
Promise.all([
fetch('/api/subscriptions/me').then(r => r.json()),
fetch('/api/subscriptions/plans').then(r => r.json()),
]).then(([subData, plansData]) => {
setSubscription(subData.subscription)
setPlans(plansData)
setLoading(false)
})
}, [])
async function handleCancel() {
if (!subscription) return
if (!confirm('Tem certeza que deseja cancelar sua assinatura?')) return
const res = await fetch(`/api/subscriptions/${subscription.id}/cancel`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ immediate: false }),
})
const data = await res.json()
setSubscription(prev => prev ? {
...prev,
cancelAtPeriodEnd: data.cancelAtPeriodEnd,
} : null)
}
async function handleReactivate() {
if (!subscription) return
const res = await fetch(`/api/subscriptions/${subscription.id}/reactivate`, {
method: 'POST',
})
const data = await res.json()
setSubscription(prev => prev ? {
...prev,
cancelAtPeriodEnd: data.cancelAtPeriodEnd,
status: data.status,
} : null)
}
async function handleChangePlan(priceId: string) {
if (!subscription) return
const res = await fetch(`/api/subscriptions/${subscription.id}/change-plan`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ newPriceId: priceId }),
})
const data = await res.json()
alert(`${data.changeType === 'upgrade' ? 'Upgrade' : 'Downgrade'} realizado!`)
window.location.reload()
}
if (loading) return <div>Carregando...</div>
if (!subscription) {
return (
<div>
<h2>Escolha um plano</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '1rem' }}>
{plans.map(plan => (
<div key={plan.id} style={{ border: '1px solid #e5e7eb', borderRadius: '8px', padding: '1.5rem' }}>
<h3>{plan.name}</h3>
{plan.prices.map(price => (
<div key={price.id} style={{ marginTop: '0.5rem' }}>
<p>{price.formatted}/{price.interval === 'month' ? 'mes' : 'ano'}</p>
<button onClick={async () => {
const res = await fetch('/api/subscriptions/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId: price.id, customerEmail: 'user@email.com' }),
})
const { checkoutUrl } = await res.json()
window.location.href = checkoutUrl
}}>
Assinar
</button>
</div>
))}
</div>
))}
</div>
</div>
)
}
return (
<div>
<h2>Minha assinatura</h2>
<div style={{ border: '1px solid #e5e7eb', borderRadius: '8px', padding: '1.5rem' }}>
<p><strong>Plano:</strong> {subscription.plan}</p>
<p><strong>Valor:</strong> {subscription.amount}/{subscription.interval === 'month' ? 'mes' : 'ano'}</p>
<p><strong>Status:</strong> {subscription.status}</p>
<p><strong>Proximo ciclo:</strong> {new Date(subscription.currentPeriodEnd).toLocaleDateString('pt-BR')}</p>
{subscription.cancelAtPeriodEnd && (
<div style={{ background: '#fef3c7', padding: '1rem', borderRadius: '4px', marginTop: '1rem' }}>
<p>Sua assinatura sera cancelada em {new Date(subscription.currentPeriodEnd).toLocaleDateString('pt-BR')}.</p>
<button onClick={handleReactivate}>Manter assinatura</button>
</div>
)}
<div style={{ marginTop: '1rem', display: 'flex', gap: '0.5rem' }}>
{!subscription.cancelAtPeriodEnd && (
<button onClick={handleCancel} style={{ color: 'red' }}>
Cancelar assinatura
</button>
)}
</div>
</div>
<h3 style={{ marginTop: '2rem' }}>Trocar plano</h3>
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
{plans.flatMap(plan =>
plan.prices.map(price => (
<button
key={price.id}
onClick={() => handleChangePlan(price.id)}
style={{ padding: '0.5rem 1rem', border: '1px solid #e5e7eb', borderRadius: '4px' }}
>
{plan.name} — {price.formatted}/{price.interval === 'month' ? 'mes' : 'ano'}
</button>
))
)}
</div>
</div>
)
}
Boas praticas
Periodo de graca (grace period)
Configure um periodo de graca para lidar com falhas temporarias de pagamento antes de suspender o acesso.// No handler de webhook — subscription.payment_failed
async function handlePaymentFailed(data: any) {
const subscription = await db.subscriptions.findOne({
chargefy_id: data.subscriptionId,
})
const failCount = (subscription.consecutive_failures || 0) + 1
await db.subscriptions.update(
{ chargefy_id: data.subscriptionId },
{ consecutive_failures: failCount }
)
if (failCount === 1) {
// Primeira falha — enviar notificacao amigavel
await sendEmail(subscription.customer_email, 'payment_failed_first', {
retryDate: data.nextRetryAt,
})
} else if (failCount === 2) {
// Segunda falha — aviso mais urgente
await sendEmail(subscription.customer_email, 'payment_failed_second', {
updatePaymentUrl: `${process.env.FRONTEND_URL}/atualizar-pagamento`,
})
} else if (failCount >= 3) {
// Terceira falha — suspender acesso
await chargefy.subscriptions.suspend(data.subscriptionId, {
reason: 'Pagamento falho apos 3 tentativas',
})
await sendEmail(subscription.customer_email, 'subscription_suspended')
}
}
Dunning (recuperacao de receita)
Estrategia recomendada para tentativas de cobranca:| Tentativa | Dias apos falha | Acao |
|---|---|---|
| 1a retentativa | +3 dias | Email amigavel + nova tentativa automatica |
| 2a retentativa | +7 dias | Email urgente + link para atualizar pagamento |
| 3a retentativa | +14 dias | Suspensao do acesso + email de suspensao |
| Cancelamento | +30 dias | Cancelamento definitivo se nao regularizado |
Tratamento de pagamento falho
// Verificar pagamentos pendentes e notificar
async function checkOverdueSubscriptions() {
const overdue = await chargefy.subscriptions.list({
status: 'past_due',
limit: 100,
})
for (const sub of overdue.items) {
const daysPastDue = Math.floor(
(Date.now() - new Date(sub.currentPeriodEnd).getTime()) / (1000 * 60 * 60 * 24)
)
if (daysPastDue > 30) {
await chargefy.subscriptions.cancel(sub.id, {
cancelAtPeriodEnd: false,
reason: `Pagamento em atraso por ${daysPastDue} dias`,
})
}
}
}
Ofereça um link direto para o cliente atualizar o metodo de pagamento. Isso aumenta significativamente a taxa de recuperacao de cobranças falhas.
Testando assinaturas no sandbox
O ambiente de sandbox permite testar todo o fluxo de assinatura sem cobranças reais.Configurar cliente para sandbox
const chargefy = new Chargefy({
accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
server: 'sandbox',
})
Cartoes de teste
| Cartao | Comportamento |
|---|---|
4111 1111 1111 1111 | Pagamento aprovado |
4000 0000 0000 0002 | Pagamento recusado |
4000 0000 0000 0069 | Pagamento expirado |
4000 0000 0000 0077 | Erro de processamento |
Simular ciclo completo
import { Chargefy } from '@chargefy/sdk'
const chargefy = new Chargefy({
accessToken: process.env.CHARGEFY_ACCESS_TOKEN!,
server: 'sandbox',
})
async function testSubscriptionFlow() {
// 1. Criar produto recorrente
const product = await chargefy.products.create({
name: '[TESTE] Plano Premium',
prices: [{
type: 'recurring',
amountType: 'fixed',
priceAmount: 1990, // R$ 19,90
priceCurrency: 'brl',
recurringInterval: 'month',
}],
})
console.log('Produto criado:', product.id)
// 2. Criar checkout
const checkout = await chargefy.checkouts.create({
productPriceId: product.prices[0].id,
customerEmail: 'teste@exemplo.com',
successUrl: 'http://localhost:3000/sucesso',
})
console.log('Checkout criado:', checkout.clientSecret)
// 3. Confirmar pagamento (sandbox — cartao de teste)
const result = await chargefy.checkouts.confirm(checkout.clientSecret, {
customerEmail: 'teste@exemplo.com',
customerName: 'Usuario Teste',
paymentMethod: 'credit_card',
card: {
cardNumber: '4111111111111111',
holderName: 'USUARIO TESTE',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
},
})
console.log('Pagamento:', result.status)
// 4. Aguardar webhook subscription.active (em ambiente real)
// No sandbox, podemos consultar diretamente
const subscriptions = await chargefy.subscriptions.list({
customerEmail: 'teste@exemplo.com',
})
const sub = subscriptions.items[0]
console.log('Assinatura:', sub.id, sub.status)
// 5. Testar cancelamento
const canceled = await chargefy.subscriptions.cancel(sub.id, {
cancelAtPeriodEnd: true,
})
console.log('Cancelamento agendado para:', canceled.currentPeriodEnd)
// 6. Reverter cancelamento
const reactivated = await chargefy.subscriptions.update(sub.id, {
cancelAtPeriodEnd: false,
})
console.log('Reativado:', reactivated.cancelAtPeriodEnd) // false
// 7. Limpar (opcional)
await chargefy.subscriptions.cancel(sub.id, { cancelAtPeriodEnd: false })
console.log('Teste concluido com sucesso!')
}
testSubscriptionFlow().catch(console.error)
No sandbox, as cobranças recorrentes nao sao processadas automaticamente. Para simular um novo ciclo, use o endpoint de teste:
curl -X POST https://api.chargefy.io/v1/sandbox/subscriptions/sub_xxxx/advance-cycle \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN"
Proximos passos
Checkout Embed
Integre o checkout diretamente na sua pagina.
Webhooks
Configuracao completa de endpoints de webhook.
Gerenciamento de clientes
API de clientes e portal do assinante.
Reembolsos
Como processar reembolsos de cobranças recorrentes.

