Plans API — Planos de Assinatura¶
Base Path: /api/v1/plans
Endpoints¶
| Metodo | Endpoint | Descricao | Auth |
|---|---|---|---|
| GET | /plans |
Listar planos ativos (publico) | Nao |
| GET | /plans/admin |
Listar todos os planos (admin) | Admin |
| POST | /plans |
Criar plano (admin) | Admin |
| PUT | /plans |
Atualizar plano (admin) | Admin |
| POST | /plans/{plan_id}/inactivate |
Inativar plano (admin) | Admin |
| POST | /plans/{plan_id}/activate |
Reativar plano (admin) | Admin |
| POST | /plans/{plan_id}/sync-stripe |
Sincronizar com Stripe (admin) | Admin |
| DELETE | /plans |
Deletar plano (admin) | Admin |
GET /plans (PUBLICO)¶
Listar planos ativos ordenados por display_order.
Uso: Pagina de pricing/signup do frontend.
Request¶
Nao requer autenticacao.
Filtro por Gateway¶
O parametro gateway filtra planos por gateway de pagamento:
# Todos os planos sincronizados (default)
GET /api/v1/plans
# Apenas planos com Stripe configurado
GET /api/v1/plans?gateway=stripe
# Apenas planos com Asaas configurado
GET /api/v1/plans?gateway=asaas
| Valor | Comportamento |
|---|---|
| (omitido) | Retorna planos sincronizados com pelo menos um gateway (stripe_price_id != null OR asaas_plan_id != null) |
stripe |
Apenas planos com stripe_price_id preenchido |
asaas |
Apenas planos com asaas_plan_id preenchido |
Quando usar o filtro
Se o frontend oferece ambos os gateways, nao use filtro — o endpoint retorna todos os planos sincronizados. Se o frontend quer exibir apenas planos disponiveis para Stripe, use ?gateway=stripe.
Response (200 OK)¶
{
"docs": [
{
"_id": "507f1f77bcf86cd799439011",
"plan_id": "plan-basic",
"stripe_product_id": "prod_RU4xxxxxxx",
"stripe_price_id": "price_1Rxxxxxxxx",
"name": "Plano Basico",
"description": "Acesso completo ao catalogo de videos com qualidade HD",
"amount": 2990,
"currency": "BRL",
"interval_unit": "MONTH",
"interval_length": 1,
"trial_days": 7,
"trial_enabled": true,
"features": [
"Qualidade HD (720p)",
"1 tela simultanea",
"Catalogo completo",
"Sem anuncios",
"7 dias gratis"
],
"max_devices": 1,
"tier": 1,
"is_active": true,
"display_order": 1
}
],
"pagination": {
"current_page": 0,
"qty_docs_page": 3,
"qty_total_docs": 3,
"qty_of_pages": 1
},
"links": {},
"msg": null
}
Campos do Plano¶
| Campo | Tipo | Descricao |
|---|---|---|
plan_id |
string |
ID interno (slug: "plan-basic") |
stripe_product_id |
string \| null |
ID do Stripe Product (prod_xxx) — null se nao sincronizado |
stripe_price_id |
string \| null |
ID do Stripe Price (price_xxx) — null se nao sincronizado |
asaas_plan_id |
string \| null |
ID do plano no Asaas — null se nao sincronizado. Read-only (populado pelo backend via sync) |
name |
string |
Nome do plano |
description |
string |
Descricao detalhada |
amount |
int |
Valor em centavos (2990 = R$ 29,90) |
currency |
string |
Codigo da moeda (BRL) |
interval_unit |
string |
Frequencia: MONTH, YEAR, WEEK, DAY |
interval_length |
int |
Intervalo (1 = mensal) |
trial_days |
int \| null |
Dias de trial gratuito |
trial_enabled |
bool |
Se trial esta ativo |
features |
string[] |
Lista de funcionalidades do plano |
max_devices |
int \| null |
Max telas simultaneas |
tier |
int |
Nivel do plano (0-3) |
is_active |
bool |
Se esta disponivel para compra |
display_order |
int |
Ordem de exibicao no frontend |
Tier (Nivel do Plano)¶
O campo tier controla o acesso a videos restritos:
| Tier | Nome | Descricao |
|---|---|---|
0 |
Universal | Acesso apenas a videos gratuitos |
1 |
Basico | Plano Basico ou superior |
2 |
Plus | Plano Plus ou superior |
3 |
Premium | Apenas Plano Premium |
Admin Endpoints¶
POST /plans — Criar Plano¶
Criar novo plano e publicar nos gateways selecionados.
POST /api/v1/plans
Authorization: Bearer eyJhbGci... (admin token)
Content-Type: application/json
[{
"plan_id": "plan-enterprise",
"name": "Plano Enterprise",
"description": "Plano para empresas com recursos avancados",
"amount": 19900,
"currency": "BRL",
"interval_unit": "MONTH",
"interval_length": 1,
"trial_days": 30,
"trial_enabled": true,
"features": [
"4K + HDR",
"10 telas simultaneas",
"API de integracao",
"Suporte prioritario"
],
"max_devices": 10,
"tier": 3,
"publish_to": ["asaas", "stripe"],
"is_active": true,
"display_order": 4
}]
Campo publish_to¶
O campo publish_to define em quais gateways o plano sera criado:
| Valor | Efeito |
|---|---|
["asaas"] |
Cria apenas no Asaas |
["stripe"] |
Cria Product + Price no Stripe |
["asaas", "stripe"] |
Cria em ambos os gateways |
Comportamento por gateway:
- Asaas: Cria o plano na API Asaas e salva
asaas_plan_id - Stripe: Cria um
Product(identidade do plano) +Price(config de cobranca recorrente) e salvastripe_product_id+stripe_price_id
Stripe: Product vs Price
No Stripe, um plano corresponde a dois objetos: Product (nome, descricao) e Price (valor, intervalo, moeda). O Price e imutavel — se o valor mudar, um novo Price e criado e o antigo arquivado automaticamente.
Response (200 OK)¶
Retorna o plano criado com os IDs dos gateways preenchidos.
Errors¶
| HTTP | Causa |
|---|---|
| 400 | Dados invalidos ou ausentes |
| 409 | plan_id ja existe |
| 502 | Falha ao criar no Asaas ou Stripe |
PUT /plans — Atualizar Plano¶
Atualizar plano existente. Sincroniza automaticamente com os gateways configurados.
PUT /api/v1/plans
Authorization: Bearer eyJhbGci... (admin token)
Content-Type: application/json
[{
"_id": "507f1f77bcf86cd799439011",
"name": "Plano Basico Atualizado",
"amount": 3490,
"features": [
"Qualidade HD (720p)",
"1 tela simultanea",
"Catalogo completo",
"Sem anuncios",
"10 dias gratis"
]
}]
Sincronizacao automatica:
- Stripe Product: Atualiza
nameedescription - Stripe Price: Se
amountmudou, cria novo Price e arquiva o antigo automaticamente
Mudanca de preco no Stripe
Ao alterar o amount, o sistema automaticamente:
- Cria um novo Stripe Price com o valor atualizado
- Arquiva o Price antigo (
active=false) - Atualiza
stripe_price_idno MongoDB
Subscriptions existentes continuam com o preco antigo. Novas subscriptions usam o novo preco.
POST /plans/{plan_id}/inactivate — Inativar Plano¶
Inativar plano (nao aceita novas subscriptions, existentes continuam).
Efeitos por gateway:
- MongoDB:
is_active = false - Stripe: Arquiva o Price (
active=false) e desativa o Product
POST /plans/{plan_id}/activate — Reativar Plano¶
Reativar plano previamente inativado.
Efeitos por gateway:
- MongoDB:
is_active = true - Stripe: Reativa o Product (
active=true)
POST /plans/{plan_id}/sync-stripe — Sincronizar com Stripe¶
Para planos criados sem Stripe, sincroniza posteriormente. Cria Product + Price no Stripe.
Response (200 OK):
{
"message": "Plan \"plan-basic\" synced to Stripe successfully.",
"plan_id": "plan-basic",
"stripe_product_id": "prod_RU4xxxxxxx",
"stripe_price_id": "price_1Rxxxxxxxx"
}
Erro 409 se o plano ja tem stripe_price_id.
Caso de uso
Planos existentes criados sem Stripe podem ser sincronizados via este endpoint, sem precisar recriar o plano.
GET /plans/admin — Listar Todos (Admin)¶
Listar todos os planos (incluindo inativos e nao sincronizados).
Suporta filtros MongoDB via query parameter.
DELETE /plans — Deletar Plano¶
Deletar plano permanentemente.
Atencao
Prefira usar POST /plans/{plan_id}/inactivate ao inves de deletar. Delecao e permanente e nao cancela subscriptions existentes.
Exemplo de UI: Pagina de Pricing¶
Exemplo Flutter/Dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
class PricingPage extends StatefulWidget {
@override
_PricingPageState createState() => _PricingPageState();
}
class _PricingPageState extends State<PricingPage> {
List<Map<String, dynamic>> plans = [];
@override
void initState() {
super.initState();
_loadPlans();
}
Future<void> _loadPlans() async {
// Sem filtro: retorna planos sincronizados com qualquer gateway
// Com filtro: GET /plans?gateway=stripe (apenas Stripe)
final response = await http.get(
Uri.parse('${AppConfig.baseUrl}/plans'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() => plans = List<Map<String, dynamic>>.from(data['docs']));
}
}
/// Verifica se um plano suporta Stripe
bool _hasStripe(Map<String, dynamic> plan) {
return plan['stripe_price_id'] != null;
}
/// Verifica se um plano suporta Asaas
bool _hasAsaas(Map<String, dynamic> plan) {
return plan['asaas_plan_id'] != null;
}
void _selectPlan(String planId, String gateway) {
Navigator.pushNamed(
context,
'/subscribe',
arguments: {'planId': planId, 'gateway': gateway},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Planos')),
body: ListView.builder(
padding: EdgeInsets.all(16),
itemCount: plans.length,
itemBuilder: (context, index) {
final plan = plans[index];
final features = List<String>.from(plan['features'] ?? []);
return Card(
margin: EdgeInsets.only(bottom: 16),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text(plan['name'], style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text(
'R\$ ${(plan['amount'] / 100).toStringAsFixed(2)}/mes',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 12),
...features.map((f) => Padding(
padding: EdgeInsets.symmetric(vertical: 2),
child: Text(f),
)),
SizedBox(height: 16),
// Botoes por gateway disponivel
if (_hasStripe(plan))
ElevatedButton(
onPressed: () => _selectPlan(plan['plan_id'], 'stripe'),
child: Text('Assinar com Cartao (Stripe)'),
),
if (_hasAsaas(plan))
OutlinedButton(
onPressed: () => _selectPlan(plan['plan_id'], 'asaas'),
child: Text('Assinar com Asaas'),
),
if (plan['trial_enabled'] == true)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
'${plan['trial_days']} dias gratis',
style: TextStyle(color: Colors.green),
),
),
],
),
),
);
},
),
);
}
}
Workflow Frontend¶
graph TD
A[Usuario visita /pricing] --> B["GET /api/v1/plans"]
B --> C[Exibir cards de planos]
C --> D[Usuario clica em Assinar]
D --> E{Qual gateway?}
E -->|Stripe| F["POST /api/v1/stripe/subscribe"]
E -->|Asaas| G["POST /api/v1/asaas/subscribe"]
Notas de Implementacao¶
Modelo Multi-Gateway¶
Cada plano pode estar sincronizado com um ou mais gateways:
| Cenario | stripe_price_id |
asaas_plan_id |
Visivel em GET /plans? |
|---|---|---|---|
Criado com publish_to: ["stripe"] |
price_xxx |
null |
Sim |
Criado com publish_to: ["asaas"] |
null |
asaas_xxx |
Sim |
Criado com publish_to: ["asaas", "stripe"] |
price_xxx |
asaas_xxx |
Sim |
| Criado sem sync (rascunho) | null |
null |
Nao |
Indices MongoDB¶
plan_id(unico)is_activeis_active + display_order(composto, para listagem)legacy_id
Ver Tambem¶
- Customer Signup — Usar plan_id no signup
- Stripe Integration — Fluxo Stripe completo
- Admin Stripe — Gerenciar subscriptions Stripe
- Asaas Integration — Integração Asaas completa