Skip to main content
O downgrade permite que clientes migrem para um plano inferior quando precisam reduzir custos. Diferente do upgrade, o downgrade normalmente entra em vigor apenas no proximo ciclo de cobranca, garantindo que o cliente aproveite o que ja pagou.

Downgrade vs cancelamento

AcaoResultado
DowngradeCliente continua assinante com menos recursos
CancelamentoCliente perde acesso ao final do periodo
Oferecer downgrade como alternativa ao cancelamento pode reduzir significativamente o churn. Considere apresentar a opcao de downgrade na pagina de cancelamento.

Opcoes de downgrade

A Chargefy suporta dois modos de downgrade:

1. Downgrade ao final do periodo (recomendado)

O cliente mantem acesso ao plano atual ate o fim do ciclo ja pago. O novo preco entra em vigor no proximo ciclo.

2. Downgrade imediato

O novo preco e aplicado imediatamente. Pode gerar credito para o proximo ciclo.

Passo 1: Agendar downgrade ao final do periodo

Via SDK

// Downgrade de Pro (R$ 99,90/mes) para Basic (R$ 49,90/mes)
const downgraded = await chargefy.subscriptions.update('sub_xxxx', {
  productPriceId: 'price_basic_mensal_xxxx',
  proration: false, // aplicar no proximo ciclo
})

console.log('Downgrade agendado!')
console.log('Plano atual:', downgraded.product.name, '(ativo ate', downgraded.currentPeriodEnd, ')')
console.log('Mudanca agendada:', downgraded.scheduledChange?.productPriceId)
console.log('Novo valor a partir de:', downgraded.scheduledChange?.effectiveDate)

Via cURL

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_basic_mensal_xxxx",
    "proration": false
  }'
Com proration: false, nenhuma cobranca ou reembolso e gerado. O cliente simplesmente passa a pagar o valor do novo plano no proximo ciclo.

Passo 2: Downgrade imediato com prorata

Se desejar aplicar o downgrade imediatamente, habilite a prorata. A Chargefy calcula o credito proporcional dos dias restantes do plano superior.

Via SDK

const downgraded = await chargefy.subscriptions.update('sub_xxxx', {
  productPriceId: 'price_basic_mensal_xxxx',
  proration: true, // aplicar imediatamente com credito
})

console.log('Downgrade imediato!')
console.log('Credito gerado: R$', (downgraded.prorationCredit / 100).toFixed(2))
console.log('Novo valor mensal: R$', (downgraded.amount / 100).toFixed(2))

Via cURL

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_basic_mensal_xxxx",
    "proration": true
  }'

Calculo do credito

Exemplo: Downgrade imediato no dia 16 de um ciclo mensal (31 dias)

Plano atual: Pro (R$ 99,90/mes)
Novo plano: Basic (R$ 49,90/mes)
Dias restantes: 15

Credito Pro: R$ 99,90 x (15/31) = R$ 48,34
Custo Basic: R$ 49,90 x (15/31) = R$ 24,15

Credito liquido: R$ 48,34 - R$ 24,15 = R$ 24,19
(aplicado como desconto no proximo ciclo)

Passo 3: Cancelar downgrade agendado

Se o cliente mudar de ideia antes do downgrade ser efetivado, cancele a mudanca agendada.

Via SDK

const restored = await chargefy.subscriptions.update('sub_xxxx', {
  cancelScheduledChange: true,
})

console.log('Downgrade cancelado — plano Pro mantido')
console.log('Mudanca agendada:', restored.scheduledChange) // null

Via cURL

curl -X PATCH https://api.chargefy.io/v1/subscriptions/sub_xxxx \
  -H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cancelScheduledChange": true
  }'

Passo 4: Revogar recursos no momento certo

A parte mais importante do downgrade e revogar o acesso aos recursos do plano superior no momento correto.

Para downgrade ao final do periodo

Revogue os recursos quando receber o webhook subscription.updated com o novo preco.
case 'subscription.updated':
  const { data } = event

  // Verificar se foi downgrade
  if (data.previousAmount && data.amount < data.previousAmount) {
    console.log('Downgrade efetivado!')
    console.log(`De R$ ${(data.previousAmount / 100).toFixed(2)} para R$ ${(data.amount / 100).toFixed(2)}`)

    // Atualizar banco de dados
    await db.subscriptions.update(
      { chargefy_id: data.id },
      {
        product_id: data.product.id,
        amount: data.amount,
        tier: data.product.metadata?.tier,
      }
    )

    // Revogar recursos do plano superior
    await downgradeFeatures(data.customer.id, data.product.metadata?.tier)

    // Notificar cliente
    await sendEmail(data.customer.email, 'plan_downgraded', {
      newPlan: data.product.name,
      newAmount: `R$ ${(data.amount / 100).toFixed(2)}`,
    })
  }
  break

Funcao de revogacao de recursos

async function downgradeFeatures(customerId: string, newTier: string) {
  const featureLimits: Record<string, { users: number; storage: number; apiCalls: number }> = {
    basic: { users: 1, storage: 5, apiCalls: 1000 },
    pro: { users: 10, storage: 50, apiCalls: 10000 },
    enterprise: { users: -1, storage: -1, apiCalls: -1 }, // ilimitado
  }

  const limits = featureLimits[newTier]
  if (!limits) return

  // Atualizar limites no banco
  await db.organizations.update(
    { customer_id: customerId },
    {
      max_users: limits.users,
      max_storage_gb: limits.storage,
      max_api_calls: limits.apiCalls,
    }
  )

  // Verificar se organizacao excede novos limites
  const org = await db.organizations.findOne({ customer_id: customerId })
  if (limits.users > 0 && org.active_users > limits.users) {
    // Notificar admin que precisa reduzir usuarios
    await sendEmail(org.admin_email, 'reduce_users_required', {
      currentUsers: org.active_users,
      maxUsers: limits.users,
      deadline: '7 dias',
    })
  }
}
Para limites de usuarios, nao desative contas automaticamente. Envie um aviso ao admin e de um prazo (ex: 7 dias) para que ele escolha quais usuarios manter.

Webhooks do downgrade

EventoQuando ocorreContexto
subscription.updatedDowngrade efetivado (inicio do novo ciclo ou imediato)data.amount contem novo valor
subscription.activeProximo ciclo cobrado com valor reduzidoValor do plano inferior

Diferenciar upgrade de downgrade no webhook

case 'subscription.updated':
  const isUpgrade = event.data.amount > (event.data.previousAmount || 0)
  const isDowngrade = event.data.amount < (event.data.previousAmount || Infinity)

  if (isUpgrade) {
    await upgradeFeatures(event.data.customer.id, event.data.product.metadata?.tier)
  } else if (isDowngrade) {
    await downgradeFeatures(event.data.customer.id, event.data.product.metadata?.tier)
  }
  break

Exemplo: Pagina de downgrade com confirmacao

src/components/DowngradePlan.tsx
import { useState } from 'react'

interface DowngradeProps {
  subscriptionId: string
  currentPlan: string
  currentAmount: number
  targetPlan: string
  targetAmount: number
  currentPeriodEnd: string
}

export function DowngradePlan({
  subscriptionId, currentPlan, currentAmount,
  targetPlan, targetAmount, currentPeriodEnd,
}: DowngradeProps) {
  const [confirmed, setConfirmed] = useState(false)
  const [processing, setProcessing] = useState(false)
  const [mode, setMode] = useState<'end_of_period' | 'immediate'>('end_of_period')

  const formatCurrency = (cents: number) => `R$ ${(cents / 100).toFixed(2)}`
  const savings = currentAmount - targetAmount

  async function handleDowngrade() {
    setProcessing(true)

    const res = await fetch(`/api/subscriptions/${subscriptionId}/change-plan`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        newPriceId: `price_${targetPlan}_mensal`,
        immediate: mode === 'immediate',
      }),
    })

    if (res.ok) {
      alert(mode === 'end_of_period'
        ? `Downgrade agendado para ${new Date(currentPeriodEnd).toLocaleDateString('pt-BR')}`
        : 'Downgrade aplicado imediatamente'
      )
      window.location.href = '/dashboard'
    } else {
      alert('Erro ao processar downgrade.')
      setProcessing(false)
    }
  }

  return (
    <div style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h2>Downgrade de plano</h2>

      <div style={{ background: '#fef3c7', padding: '1rem', borderRadius: '8px', marginBottom: '1.5rem' }}>
        <p><strong>Atencao:</strong> Ao fazer downgrade, voce perdera acesso aos recursos do plano {currentPlan}.</p>
      </div>

      <div style={{ border: '1px solid #e5e7eb', borderRadius: '8px', padding: '1.5rem', marginBottom: '1rem' }}>
        <p><strong>Plano atual:</strong> {currentPlan}{formatCurrency(currentAmount)}/mes</p>
        <p><strong>Novo plano:</strong> {targetPlan}{formatCurrency(targetAmount)}/mes</p>
        <p style={{ color: '#16a34a' }}><strong>Economia:</strong> {formatCurrency(savings)}/mes</p>
      </div>

      <div style={{ marginBottom: '1.5rem' }}>
        <p><strong>Quando aplicar?</strong></p>
        <label style={{ display: 'block', padding: '0.5rem 0' }}>
          <input
            type="radio"
            checked={mode === 'end_of_period'}
            onChange={() => setMode('end_of_period')}
          />
          {' '}Ao final do periodo atual ({new Date(currentPeriodEnd).toLocaleDateString('pt-BR')})
        </label>
        <label style={{ display: 'block', padding: '0.5rem 0' }}>
          <input
            type="radio"
            checked={mode === 'immediate'}
            onChange={() => setMode('immediate')}
          />
          {' '}Imediatamente (credito proporcional aplicado)
        </label>
      </div>

      <label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '1rem' }}>
        <input type="checkbox" checked={confirmed} onChange={e => setConfirmed(e.target.checked)} />
        Entendo que perderei acesso aos recursos do plano {currentPlan}
      </label>

      <button
        onClick={handleDowngrade}
        disabled={!confirmed || processing}
        style={{
          width: '100%', padding: '0.75rem',
          background: confirmed ? '#dc2626' : '#d1d5db',
          color: 'white', border: 'none', borderRadius: '6px',
          cursor: confirmed && !processing ? 'pointer' : 'not-allowed',
        }}
      >
        {processing ? 'Processando...' : 'Confirmar downgrade'}
      </button>
    </div>
  )
}

Boas praticas

  • Prefira downgrade ao final do periodo para evitar complexidade de reembolsos
  • Avise com antecedencia quais recursos serao perdidos antes de confirmar
  • De prazo para ajustes quando o cliente excede os limites do novo plano (ex: usuarios)
  • Ofereça alternativas — desconto temporario pode ser mais vantajoso que downgrade
  • Registre o motivo do downgrade para analise de churn

Proximos passos

Upgrade de Assinatura

Implemente o fluxo reverso — upgrade de plano.

Multiplas Assinaturas

Permita que clientes tenham mais de uma assinatura ativa.