Estrategias de modelagem
| Estrategia | Quando usar | Exemplo |
|---|---|---|
| Multiplos precos em um produto | Mesmo recurso, intervalos diferentes | Plano Pro mensal (R99,90)eanual(R 999,00) |
| Produtos separados | Recursos diferentes por nivel | Basic, Pro e Enterprise com funcionalidades distintas |
Passo 1: Criar produto com multiplos precos
O jeito mais simples de criar variantes e adicionar varios precos ao mesmo produto — cada preco representa um intervalo ou valor diferente.Via SDK
const product = await chargefy.products.create({
name: 'Plataforma SaaS',
description: 'Ferramenta completa de gestao para empresas',
prices: [
// Basic - Mensal
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 4990, // R$ 49,90/mes
priceCurrency: 'brl',
recurringInterval: 'month',
metadata: { tier: 'basic' },
},
// Basic - Anual
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 47900, // R$ 479,00/ano (~20% desconto)
priceCurrency: 'brl',
recurringInterval: 'year',
metadata: { tier: 'basic' },
},
// Pro - Mensal
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 9990, // R$ 99,90/mes
priceCurrency: 'brl',
recurringInterval: 'month',
metadata: { tier: 'pro' },
},
// Pro - Anual
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 95900, // R$ 959,00/ano (~20% desconto)
priceCurrency: 'brl',
recurringInterval: 'year',
metadata: { tier: 'pro' },
},
// Enterprise - Mensal
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 29990, // R$ 299,90/mes
priceCurrency: 'brl',
recurringInterval: 'month',
metadata: { tier: 'enterprise' },
},
// Enterprise - Anual
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 287900, // R$ 2.879,00/ano (~20% desconto)
priceCurrency: 'brl',
recurringInterval: 'year',
metadata: { tier: 'enterprise' },
},
],
})
console.log('Produto criado:', product.id)
console.log('Total de precos:', product.prices.length)
Via cURL
curl -X POST https://api.chargefy.io/v1/products \
-H "Authorization: Bearer $CHARGEFY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Plataforma SaaS",
"description": "Ferramenta completa de gestao para empresas",
"prices": [
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 4990,
"priceCurrency": "brl",
"recurringInterval": "month",
"metadata": { "tier": "basic" }
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 47900,
"priceCurrency": "brl",
"recurringInterval": "year",
"metadata": { "tier": "basic" }
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 9990,
"priceCurrency": "brl",
"recurringInterval": "month",
"metadata": { "tier": "pro" }
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 95900,
"priceCurrency": "brl",
"recurringInterval": "year",
"metadata": { "tier": "pro" }
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 29990,
"priceCurrency": "brl",
"recurringInterval": "month",
"metadata": { "tier": "enterprise" }
},
{
"type": "recurring",
"amountType": "fixed",
"priceAmount": 287900,
"priceCurrency": "brl",
"recurringInterval": "year",
"metadata": { "tier": "enterprise" }
}
]
}'
Passo 2: Listar variantes de um produto
Para exibir as opcoes ao cliente, busque o produto e agrupe os precos por tier e intervalo.Via SDK
const product = await chargefy.products.get('prod_xxxx')
// Agrupar precos por tier
const tiers = product.prices.reduce((acc, price) => {
const tier = price.metadata?.tier || 'default'
if (!acc[tier]) acc[tier] = []
acc[tier].push({
id: price.id,
amount: price.priceAmount,
formatted: `R$ ${(price.priceAmount / 100).toFixed(2)}`,
interval: price.recurringInterval,
})
return acc
}, {} as Record<string, any[]>)
console.log('Variantes:', JSON.stringify(tiers, null, 2))
// {
// "basic": [
// { "id": "price_xxx", "formatted": "R$ 49,90", "interval": "month" },
// { "id": "price_yyy", "formatted": "R$ 479,00", "interval": "year" }
// ],
// "pro": [...],
// "enterprise": [...]
// }
Passo 3: Criar checkout para uma variante
O checkout e criado usando oproductPriceId da variante escolhida pelo cliente.
// Cliente escolheu: Pro Anual
const checkout = await chargefy.checkouts.create({
productPriceId: 'price_pro_anual_xxxx',
customerEmail: '[email protected]',
successUrl: 'https://meuapp.com.br/assinatura/sucesso',
})
console.log('Checkout URL:', checkout.url)
Exemplo completo: Tabela de precos React
src/components/PricingTable.tsx
import { useState, useEffect } from 'react'
interface Price {
id: string
amount: number
formatted: string
interval: string
}
interface Tier {
name: string
key: string
description: string
features: string[]
prices: Price[]
highlighted?: boolean
}
const TIER_CONFIG: Record<string, Omit<Tier, 'prices'>> = {
basic: {
name: 'Basic',
key: 'basic',
description: 'Para profissionais autonomos',
features: ['1 usuario', '1.000 transacoes/mes', 'Suporte por email', 'Relatorios basicos'],
},
pro: {
name: 'Pro',
key: 'pro',
description: 'Para pequenas e medias empresas',
features: ['Ate 10 usuarios', '10.000 transacoes/mes', 'Suporte prioritario', 'Relatorios avancados', 'API completa'],
highlighted: true,
},
enterprise: {
name: 'Enterprise',
key: 'enterprise',
description: 'Para grandes operacoes',
features: ['Usuarios ilimitados', 'Transacoes ilimitadas', 'Suporte dedicado', 'SLA 99,9%', 'API completa', 'Webhooks customizados'],
},
}
export function PricingTable() {
const [tiers, setTiers] = useState<Tier[]>([])
const [billingInterval, setBillingInterval] = useState<'month' | 'year'>('month')
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/products/prod_xxxx')
.then(r => r.json())
.then(product => {
// Agrupar precos por tier
const grouped: Record<string, Price[]> = {}
for (const price of product.prices) {
const tier = price.metadata?.tier || 'default'
if (!grouped[tier]) grouped[tier] = []
grouped[tier].push({
id: price.id,
amount: price.priceAmount,
formatted: `R$ ${(price.priceAmount / 100).toFixed(2)}`,
interval: price.recurringInterval,
})
}
const tierList = Object.entries(TIER_CONFIG).map(([key, config]) => ({
...config,
prices: grouped[key] || [],
}))
setTiers(tierList)
setLoading(false)
})
}, [])
async function handleSubscribe(priceId: string) {
const res = await fetch('/api/subscriptions/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId, customerEmail: '[email protected]' }),
})
const { checkoutUrl } = await res.json()
window.location.href = checkoutUrl
}
if (loading) return <div>Carregando planos...</div>
return (
<div>
<h2 style={{ textAlign: 'center' }}>Escolha seu plano</h2>
{/* Toggle mensal/anual */}
<div style={{ display: 'flex', justifyContent: 'center', gap: '0.5rem', marginBottom: '2rem' }}>
<button
onClick={() => setBillingInterval('month')}
style={{
padding: '0.5rem 1rem',
borderRadius: '4px',
border: '1px solid #d1d5db',
background: billingInterval === 'month' ? '#2563eb' : 'white',
color: billingInterval === 'month' ? 'white' : '#374151',
cursor: 'pointer',
}}
>
Mensal
</button>
<button
onClick={() => setBillingInterval('year')}
style={{
padding: '0.5rem 1rem',
borderRadius: '4px',
border: '1px solid #d1d5db',
background: billingInterval === 'year' ? '#2563eb' : 'white',
color: billingInterval === 'year' ? 'white' : '#374151',
cursor: 'pointer',
}}
>
Anual <span style={{ fontSize: '0.75rem' }}>(-20%)</span>
</button>
</div>
{/* Cards de planos */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1.5rem', maxWidth: '900px', margin: '0 auto' }}>
{tiers.map(tier => {
const price = tier.prices.find(p => p.interval === billingInterval)
if (!price) return null
return (
<div
key={tier.key}
style={{
border: tier.highlighted ? '2px solid #2563eb' : '1px solid #e5e7eb',
borderRadius: '12px',
padding: '2rem',
position: 'relative',
}}
>
{tier.highlighted && (
<span style={{
position: 'absolute', top: '-12px', left: '50%', transform: 'translateX(-50%)',
background: '#2563eb', color: 'white', padding: '2px 12px', borderRadius: '12px', fontSize: '0.75rem',
}}>
Mais popular
</span>
)}
<h3>{tier.name}</h3>
<p style={{ color: '#6b7280', fontSize: '0.875rem' }}>{tier.description}</p>
<p style={{ fontSize: '2rem', fontWeight: 'bold', margin: '1rem 0' }}>
{price.formatted}
<span style={{ fontSize: '1rem', fontWeight: 'normal', color: '#6b7280' }}>
/{billingInterval === 'month' ? 'mes' : 'ano'}
</span>
</p>
<button
onClick={() => handleSubscribe(price.id)}
style={{
width: '100%', padding: '0.75rem',
background: tier.highlighted ? '#2563eb' : 'white',
color: tier.highlighted ? 'white' : '#2563eb',
border: '1px solid #2563eb', borderRadius: '6px', cursor: 'pointer',
}}
>
Assinar {tier.name}
</button>
<ul style={{ marginTop: '1.5rem', listStyle: 'none', padding: 0 }}>
{tier.features.map(feature => (
<li key={feature} style={{ padding: '0.25rem 0', fontSize: '0.875rem' }}>
{feature}
</li>
))}
</ul>
</div>
)
})}
</div>
</div>
)
}
Adicionar novas variantes a um produto existente
// Adicionar um preco trimestral ao produto existente
const updatedProduct = await chargefy.products.update('prod_xxxx', {
prices: [
// Manter precos existentes (pelo ID)
{ id: 'price_basic_mensal_xxxx' },
{ id: 'price_basic_anual_xxxx' },
// Adicionar novo preco
{
type: 'recurring',
amountType: 'fixed',
priceAmount: 12990, // R$ 129,90/trimestre
priceCurrency: 'brl',
recurringInterval: 'quarter',
metadata: { tier: 'basic' },
},
],
})
Ao atualizar precos de um produto, sempre inclua os IDs dos precos existentes que deseja manter. Precos nao listados serao arquivados.
Boas praticas
- Use metadata para categorizar precos por tier, facilitando a filtragem no frontend
- Ofereça desconto anual de 15-20% para incentivar compromissos mais longos
- Destaque o plano mais popular visualmente para guiar a decisao do cliente
- Mantenha no maximo 3-4 tiers para evitar paralisia de escolha
Proximos passos
Upgrade de Assinatura
Permita que clientes troquem entre variantes.
Precificacao por Assento
Combine variantes com cobranca por assento.

