API de Dashboard Financeiro¶
Endpoints para o painel administrativo financeiro. Fornece KPIs, series temporais para graficos, lista de inadimplentes, analise de churn e exportacao Excel.
Prefixo: /api/v1/admin/finance
Autenticacao: Bearer token (JWT) + user_type='admin'
Endpoints¶
| Metodo | Endpoint | Descricao |
|---|---|---|
| GET | /admin/finance/overview |
KPIs consolidados (MRR, ARR, inadimplentes, churn) |
| GET | /admin/finance/revenue |
Serie temporal de receita (diario/mensal) |
| GET | /admin/finance/payments |
Dinheiro recebido por dia/mes (razao de pagamentos) |
| GET | /admin/finance/overdue |
Lista de clientes inadimplentes |
| GET | /admin/finance/churn |
Analise de churn mensal |
| GET | /admin/finance/export |
Exportar dados financeiros em Excel (.xlsx) |
Filtros de periodo (ADR-0128)¶
Todas as rotas de agregacao aceitam recorte por data e devolvem, no corpo, o recorte que de fato aplicaram.
| Parametro | Tipo | Onde vale | Sobre qual data |
|---|---|---|---|
start / end |
YYYY-MM-DD |
overview, revenue, payments, churn, overdue, ltv | ver tabela abaixo |
gateway |
asaas | stripe | pagarme | pagseguro |
overview, revenue, payments, churn, overdue | — |
payment_method |
credit_card | boleto | pix |
overview, revenue, payments, churn, overdue | — |
start/end sao dias de Brasilia (ADR-0137): start=2026-10-22 comeca as 00:00 BRT
(03:00 UTC) e end=2026-10-22 vai ate 23:59:59 BRT (02:59:59 UTC do dia 23). end e
inclusivo. O cliente manda o dia; a API converte. Instante com hora (...T03:00:00Z)
e group_by=hour nao sao aceitos: o recorte por dia de Brasilia ja e o que eles
reconstruiriam (pedido de 23/10, respondido com o caso 77 x 72 na secao 2c).
| Rota | start/end recortam por |
|---|---|
overview |
os campos de movimento (ver secao 1) |
revenue |
o periodo da serie |
payments, payments/entries |
a primeira data em que o gateway confirmou o pagamento |
churn |
o intervalo; vence months quando os dois vem juntos |
overdue |
next_billing_date (data de vencimento) |
ltv |
created_at do cliente (safra de aquisicao) |
Param desconhecido devolve 422¶
Ate a ADR-0128, param de query nao declarado era descartado em silencio: a rota
respondia 200 e o painel nao tinha como distinguir isso de filtro aplicado.
Agora cada rota declara o que sabe aplicar, e o resto e 422:
applied_filters¶
overview, revenue, churn, overdue e ltv devolvem o recorte resolvido
— numa rota com janela padrao, ele vem preenchido, nao null:
{
"applied_filters": {
"start": "2026-09-01",
"end": "2026-09-12",
"gateway": null,
"payment_method": null
}
}
O ltv nao aceita gateway/payment_method: o LTV e por cliente, que pode ter
trocado de gateway ao longo da vida — aceitar o parametro seria descarta-lo.
null so quando nao existe resposta (ADR-0129)¶
Nenhum campo vem null por dificuldade de calculo. Sob filtro, todos respondem:
| Campo | Como responde ao recorte |
|---|---|
overview.total_customers |
clientes distintos nas assinaturas do recorte (sem filtro, total de customers) |
overview.payment_success_rate |
recorta por payload.payment.billingType da cobranca |
churn.by_reason.admin_cancelled |
conta as assinaturas apontadas pelo log de auditoria |
churn.by_reason.customer_cancelled |
total de canceladas menos admin e chargeback |
Os motivos de by_reason particionam o total sob qualquer filtro.
Quando nao ha resposta, payment_success_rate sai da resposta — nao vem
null (ADR-0130). Sao dois casos:
- nenhum desfecho de cobranca na janela;
gateway=stripecombinado compayment_method: os eventosinvoice.*nao guardam o metodo da cobranca, e ocus_...deles nao casa com nenhumstripe_customer_idnosso — o dado nao existe em lugar nenhum.
Distinga ausente de zero: body.get('payment_success_rate') ausente
significa "nao apuravel", nunca "0%".
Taxa de sucesso = desfecho da cobranca, por vencimento (ADR-0136)¶
payment_success_rate (Asaas) = das cobrancas que venceram na janela, pagas /
(pagas + vencidas em aberto).
- Conta cobrancas, nao eventos: estado de cada
payload.payment.idno log. - Paga = teve
PAYMENT_CONFIRMEDouPAYMENT_RECEIVED(a estornada tambem foi paga). A que venceu e depois foi paga conta so como sucesso. - Fora: cobranca apagada sem pagamento (a Asaas nao a lista) e pendente.
- Stripe:
invoice.paid/ (invoice.paid+invoice.payment_failed) na janela.
Ate 14/09/2026 a apagada contava como falha: 52,5% no painel x 66,8% na Asaas.
-------|---------------|
| overview.payment_success_rate | payment_method no filtro |
| overview.total_customers | gateway ou payment_method no filtro |
| churn.by_reason.admin_cancelled | gateway ou payment_method no filtro |
| churn.by_reason.customer_cancelled | idem (deriva do anterior) |
Trate null como "nao apuravel neste recorte", nao como zero.
1. GET /admin/finance/overview¶
KPIs financeiros consolidados. Dados prontos para cards de dashboard.
GET /api/v1/admin/finance/overview?start=2026-09-01&end=2026-09-12
Authorization: Bearer <admin_token>
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
start |
YYYY-MM-DD |
30 dias atras | Inicio da janela de movimento |
end |
YYYY-MM-DD |
hoje | Fim da janela (inclusivo) |
gateway |
string |
— | asaas, stripe, pagarme ou pagseguro (legado, 1 registro) |
payment_method |
string |
— | credit_card, boleto ou pix |
Duas naturezas de campo convivem aqui, e o painel precisa rotular a diferenca:
- Saldo (
mrr,arr,active_subscriptions,suspended_subscriptions,by_plan,by_payment_method,total_customers,average_ticket,overdue_count): estado da base agora.start/endnao se aplicam;gateway/payment_methodsim. - Movimento (
churn_rate,gross_revenue,net_revenue,refunded_in_period,chargebacks_in_period,payment_success_rate): seguem a janela pedida. Dinheiro (gross_revenue,net_revenue,refunded_in_period) vem do razao de pagamentos (ADR-0132) — o mesmo da secao 2b. - Janela no nome (
churn_rate_30d,net_revenue_30d): sempre 30 dias, qualquer que seja o filtro. Campo que declara periodo no nome nao devolve outro. - Acumulado (
total_refunded,total_chargebacks): historico inteiro, do mesmo razao de pagamentos (inclui estorno de ingresso).
Response (200 OK)¶
{
"mrr": 384070,
"mrr_stripe": 246760,
"mrr_asaas": 137310,
"mrr_pagarme": 0,
"arr": 4608840,
"total_customers": 13797,
"active_subscriptions": 193,
"overdue_count": 34,
"churn_rate_30d": 4.0,
"total_refunded": 0,
"total_chargebacks": 0,
"net_revenue_30d": 384070,
"average_ticket": 1990,
"payment_success_rate": 19.7,
"by_payment_method": {
"credit_card": { "count": 112, "mrr": 222880 },
"pix": { "count": 81, "mrr": 161190 }
},
"by_plan": {
"plan-basic": { "count": 193, "mrr": 384070, "name": "Plano Mensal Basico" }
}
}
Campos¶
| Campo | Tipo | Descricao |
|---|---|---|
mrr |
int |
Monthly Recurring Revenue (centavos) |
mrr_stripe |
int |
MRR somente Stripe (centavos) |
mrr_asaas |
int |
MRR somente Asaas (centavos) |
mrr_pagarme |
int |
MRR somente Pagar.me (centavos). Só assinatura de cartão conta: PIX do Pagar.me é pedido avulso, sem contrato que renove |
arr |
int |
Annual Recurring Revenue = MRR * 12 (centavos) |
total_customers |
int |
Sem filtro, total de customers. Sob filtro, clientes distintos no recorte |
active_subscriptions |
int |
Pagantes (ADR-0135): Asaas = assinatura viva, contrato nao encerrado e cobranca vigente (a mais recente ja vencida ou paga) paga sem chargeback; Stripe = acesso efetivo. Contrato conta uma vez. Ate 14/09/2026 era status=active. Lista linha a linha, com o motivo de quem nao paga: /finance/payers (secao 2d) |
suspended_subscriptions |
int |
Assinaturas com status=suspended agora, qualquer motivo, inclusive sem cobranca aberta. = suspended_with_open_debt + suspended_without_open_debt (ADR-0173). Ate 23/10/2026 a descricao dizia "3+ vencidas", o que valia para 642 de 2.703 |
suspended_with_open_debt |
int |
Suspensas com cobranca vencida em aberto (estao em /finance/overdue). Pagar reativa |
suspended_without_open_debt |
int |
Suspensas sem cobranca aberta: em geral o contrato ja foi encerrado no gateway (ADR-0116) |
overdue_count |
int |
Mesmo conjunto de /finance/overdue sem busca nem janela (ADR-0134); so assinatura viva (ADR-0173) |
churn_rate_30d |
float |
Taxa de churn ultimos 30 dias (%) |
total_refunded |
int |
Estornos, acumulado historico (centavos), do razao. Ate 14/09/2026 somava subscriptions.total_refunded, que perdia estorno de ingresso |
total_chargebacks |
int |
Chargebacks, acumulado historico (quantidade), do razao |
net_revenue_30d |
int |
Recebido - estornado - chargeback nos ultimos 30 dias (centavos). Ate 14/09/2026 era MRR - estornos |
average_ticket |
int |
Ticket medio = MRR / pagantes (centavos) |
payment_success_rate |
float (pode faltar) |
Das cobrancas que venceram na janela, pagas / (pagas + vencidas em aberto). Ausente quando nao ha o que medir |
by_payment_method |
dict |
Breakdown por metodo (credit_card, pix, boleto) |
by_plan |
dict |
Breakdown por plano (count, mrr, name) |
churn_rate |
float |
Taxa de churn na janela pedida (%) |
gross_revenue |
int |
Pagamentos recebidos na janela (centavos) |
net_revenue |
int |
Recebido - estornado - chargeback na janela (centavos). Ate 14/09/2026 era MRR - estornos — um saldo que nao mudava com o mes |
refunded_in_period |
int |
Estornos na janela, pela data do estorno (centavos) |
chargebacks_in_period |
int |
Chargebacks na janela (quantidade), do razao — a mesma fonte que net_revenue desconta |
applied_filters |
object |
Recorte efetivamente aplicado |
Uso no frontend (cards)¶
// Converter centavos para reais
String formatBRL(int cents) => 'R\$ ${(cents / 100).toStringAsFixed(2)}';
// Cards sugeridos:
// - MRR: formatBRL(overview.mrr)
// - ARR: formatBRL(overview.arr)
// - Assinaturas ativas: overview.active_subscriptions
// - Inadimplentes: overview.overdue_count (vermelho se > 0)
// - Churn 30d: "${overview.churn_rate_30d}%"
// - Ticket medio: formatBRL(overview.average_ticket)
2. GET /admin/finance/revenue¶
Serie temporal de receita. Dados formatados para graficos de linha/barra.
Semantica mudou em 14/09/2026 (ADR-0132)
gross, refunds, chargebacks, net, stripe e asaas sao fluxo — dinheiro
que entrou/saiu no periodo, do razao de pagamentos — com o mesmo significado em
daily e monthly: somar os dias da o mes. Antes, as duas granularidades devolviam o
MRR das assinaturas vivas (no diario ele se repetia a cada dia; somar setembro dava 12x
o mes). Esse saldo continua disponivel em active_mrr, que nao se soma.
GET /api/v1/admin/finance/revenue?period=monthly&start=2026-01&end=2026-03
Authorization: Bearer <admin_token>
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
period |
string |
monthly |
monthly ou daily |
start |
string |
6 meses atras | Inicio (YYYY-MM mensal, YYYY-MM-DD diario) |
end |
string |
hoje | Fim (mesmo formato) |
gateway |
string |
— | asaas, stripe, pagarme ou pagseguro (legado, 1 registro) |
payment_method |
string |
— | credit_card, boleto ou pix |
Response (200 OK)¶
{
"period": "monthly",
"data": [
{
"date": "2026-01",
"gross": 79600,
"refunds": 0,
"chargebacks": 0,
"net": 79600,
"new_subscriptions": 7,
"cancellations": 0,
"stripe": 45000,
"asaas": 34600,
"pagarme": 0,
"active_mrr": 81590
},
{
"date": "2026-02",
"gross": 246760,
"refunds": 0,
"chargebacks": 0,
"net": 246760,
"new_subscriptions": 85,
"cancellations": 3,
"stripe": 150000,
"asaas": 96760
},
{
"date": "2026-03",
"gross": 218900,
"refunds": 0,
"chargebacks": 0,
"net": 218900,
"new_subscriptions": 0,
"cancellations": 0,
"stripe": 130000,
"asaas": 88900
}
],
"totals": {
"gross": 545260,
"refunds": 0,
"chargebacks": 0,
"net": 545260,
"new_subscriptions": 92,
"cancellations": 3
}
}
Uso no frontend (graficos)¶
// Grafico de linha: Receita mensal
// X: data[].date
// Y1: data[].gross (dinheiro recebido no periodo)
// Y2: data[].net (recebido - estornos - chargebacks)
// Nao somar data[].active_mrr: e saldo, nao fluxo
// Grafico de barras empilhadas: Stripe vs Asaas
// X: data[].date
// Stack 1: data[].stripe
// Stack 2: data[].asaas
// Grafico de linha: Novas vs Cancelamentos
// X: data[].date
// Y1: data[].new_subscriptions (verde)
// Y2: data[].cancellations (vermelho)
2b. GET /admin/finance/payments¶
Dinheiro movimentado por dia ou mes: pagamentos, estornos e chargebacks (ADR-0132). E a resposta para "quanto entrou de dinheiro neste periodo".
GET /api/v1/admin/finance/payments?start=2026-09-01&end=2026-09-13&group_by=day
Authorization: Bearer <admin_token>
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
start |
YYYY-MM-DD |
30 dias atras | Inicio (inclusivo) |
end |
YYYY-MM-DD |
hoje | Fim (inclusivo) |
group_by |
string |
day |
day ou month |
gateway |
string |
— | asaas, stripe, pagarme ou pagseguro |
payment_method |
string |
— | credit_card, boleto ou pix |
Response (200 OK)¶
{
"group_by": "day",
"coverage_start": "2026-03-20",
"data": [
{
"date": "2026-09-13",
"paid_cents": 107460,
"count": 54,
"refunded_cents": 0,
"refund_count": 0,
"chargeback_cents": 0,
"chargeback_count": 0,
"net_cents": 107460,
"by_method": { "pix": { "paid_cents": 59700, "count": 30 }, "credit_card": { "paid_cents": 47760, "count": 24 } },
"by_gateway": { "asaas": { "paid_cents": 107460, "count": 54 } },
"by_source": { "subscription": { "paid_cents": 107460, "count": 54 } }
}
],
"totals": { "paid_cents": 107460, "count": 54, "net_cents": 107460, "...": "mesmos campos, sem date" },
"applied_filters": { "start": "2026-09-01", "end": "2026-09-13", "gateway": null, "payment_method": null }
}
Regras¶
- Um pagamento conta uma vez. O cartao na Asaas emite
PAYMENT_CONFIRMEDe, ~30 dias depois,PAYMENT_RECEIVED: conta na primeira data, mesmo que a segunda caia em outro mes. - Estorno conta na data do estorno; estorno parcial soma so o que saiu.
datatraz todos os periodos da janela, inclusive os sem movimento (zeros). Comgroup_by=month, o primeiro e o ultimo mes cobrem so a parte dentro da janela — usestartno dia 1 para meses inteiros;applied_filtersdiz o recorte.- Data do cartao = confirmacao, nao liquidacao: o pagamento entra no dia em que o cliente pagou. O dinheiro do cartao cai na conta ~30 dias depois, entao o total do mes nao bate com o extrato bancario (agosto/2026: R$ 46,9 mil confirmados x R$ 44,5 mil liquidados).
by_source:subscriptionouticket(Ticket Store).by_method/by_gateway/by_sourcequebram so os pagamentos.- Fatura da Stripe entra como metodo
unknown: o payload nao informa o metodo (ADR-0130). PIX pela Stripe (PaymentIntent) nao e notificado ao endpoint e fica fora. coverage_start: primeiro pagamento registrado. Antes dele, zero nao quer dizer que nao entrou dinheiro. Sai da resposta quando nao ha pagamento registrado.- Dia e mes de Brasilia (ADR-0137).
2c. GET /admin/finance/payments/entries¶
Os mesmos pagamentos que o overview soma em gross_revenue, um por linha (ADR-0172).
totals.paid_cents e igual ao gross_revenue do mesmo recorte.
GET /api/v1/admin/finance/payments/entries?start=2026-10-22&end=2026-10-22&page=0&limit=100
Authorization: Bearer <admin_token>
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
start / end |
YYYY-MM-DD |
30 dias | Dias de Brasilia, end inclusivo |
gateway / payment_method |
string |
— | Como em /payments |
page |
int |
0 |
Pagina, base 0 |
limit |
int |
100 |
Linhas por pagina (max 500) |
{
"total": 77, "page": 0, "limit": 100, "coverage_start": "2026-03-20",
"totals": { "count": 77, "paid_cents": 153230 },
"applied_filters": { "start": "2026-10-22", "end": "2026-10-22", "gateway": null, "payment_method": null },
"data": [
{
"payment_id": "pay_yvniakbrupgc9gck", "gateway": "asaas", "source": "subscription",
"payment_method": "credit_card", "amount_cents": 1990,
"confirmed_at": "2026-10-22T10:06:00Z", "kind": "renewal", "refunded_cents": 0,
"subscription_id": "6a…", "customer_id": "69…",
"customer_created_at": "2025-07-28T13:51:21Z", "customer_utm_source": null
}
]
}
- Data = primeira confirmacao do gateway. No cartao Asaas e a captura, nao o
dueDate: cobranca vencida recapturada conta no dia em que o dinheiro entrou. Em 22/10 foram 5 assim (vencimentos de 08/08 a 21/10,confirmedDate22/10) — datar pelodueDateda 72 em vez de 77 e nao fecha com o overview. kind:first_charge= primeiro pagamento da assinatura (Stripe:billing_reason; Asaas: o primeiro no razao, contando a avulsa do 1º mes com desconto). Assinatura que comecou antes decoverage_startsairenewal.null= ingresso (source=ticket) ou cobranca sem assinatura identificada.- Cadastro novo x conta antiga:
customer_created_atdentro do periodo. refunded_cents: estornado desta cobranca ate agora, em qualquer data.- Ordem:
confirmed_at. Instantes em UTC (Z); converter para exibir.
2d. GET /admin/finance/payers¶
Quem compoe overview.active_subscriptions e overview.mrr, uma linha por assinatura
viva (active, pending, suspended), com o motivo de quem nao paga (ADR-0174).
totals.paying e totals.mrr_cents sao iguais aos do overview no mesmo recorte.
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
gateway / payment_method |
string |
— | Como no overview |
paying |
bool |
— | true so pagantes, false so nao pagantes |
page / limit |
int |
0 / 100 |
Base 0; limit max 500 |
{
"total": 6136, "page": 0, "limit": 100,
"totals": { "paying": 2126, "mrr_cents": 4211624, "not_paying": 4010,
"not_paying_by_reason": { "contract_ended_at_gateway": 2253, "current_charge_unpaid": 1701 } },
"data": [
{ "subscription_id": "6a…", "customer_id": "69…", "gateway": "asaas", "payment_method": "pix",
"status": "suspended", "is_paying": false, "reason": "current_charge_unpaid",
"monthly_cents": null, "months": null, "asaas_subscription_id": "sub_…", "stripe_subscription_id": null }
]
}
reason |
Significado |
|---|---|
contract_ended_at_gateway |
Contrato encerrado na Asaas (sincronia diaria) |
no_current_charge |
Nenhuma cobranca venceu nem foi paga ainda |
current_charge_unpaid |
A cobranca vigente (mais recente ja vencida) nao foi paga |
chargeback |
A vigente foi paga e contestada |
stripe_no_contract |
Stripe sem assinatura recorrente (PIX avulso) |
stripe_not_active |
A Stripe nao diz active |
counted_in_another_subscription |
Outro documento do mesmo contrato ja conta (DEBT-100) |
- Pagante nao e
status=active. Suspensa com a vigente paga conta; ativa com a vigente em aberto nao. Saldo de agora: semstart/end.
3. GET /admin/finance/overdue¶
Lista paginada de clientes com pagamentos em atraso. So assinatura viva (active,
pending, suspended — ADR-0173): cancelada ou expirada com cobranca aberta e residuo no
gateway, a encerrar la, nao cliente a acionar. Ate 23/10/2026 entravam (64 canceladas e 1
cobranca de ingresso).
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
page |
int |
0 |
Pagina (zero-indexed) |
limit |
int |
20 |
Itens por pagina (max 100) |
search |
string |
— | Busca server-side por username, e-mail ou nome |
start |
YYYY-MM-DD |
— | Vencimento a partir de (Asaas: da cobranca; demais: next_billing_date) |
end |
YYYY-MM-DD |
— | Vencimento ate (inclusivo) |
gateway |
string |
— | asaas, stripe, pagarme ou pagseguro (legado, 1 registro) |
payment_method |
string |
— | credit_card, boleto ou pix |
Response (200 OK)¶
{
"total_overdue": 34,
"total_amount_at_risk": 39800,
"customers": [
{
"customer_id": "69b1a2b3c4d5e6f7a8b9c0d1",
"username": "joao_silva",
"email": "joao@example.com",
"full_name": "Joao da Silva",
"plan_id": "plan-basic",
"plan_name": "Plano Mensal Basico",
"gateway": "asaas",
"payment_method": "credit_card",
"payment_failures": 3,
"last_payment_at": "2026-02-15T00:00:00+00:00",
"next_billing_date": "2026-03-15T00:00:00+00:00",
"days_overdue": 12,
"subscription_status": "suspended",
"amount": 3980,
"open_charges": 2,
"oldest_due_date": "2026-03-15"
}
]
}
Campos do customer inadimplente¶
| Campo | Tipo | Descricao |
|---|---|---|
customer_id |
string |
ID do customer (MongoDB ObjectId) |
username |
string |
Username |
email |
string |
|
full_name |
string |
Nome completo |
plan_id |
string |
Slug do plano |
plan_name |
string |
Nome do plano |
gateway |
string |
Gateway (stripe/asaas) |
payment_method |
string |
Metodo (credit_card/pix/boleto) |
payment_failures |
int |
Falhas consecutivas de pagamento |
last_payment_at |
string |
Ultimo pagamento (ISO 8601) |
next_billing_date |
string |
Proxima cobranca (ISO 8601) |
days_overdue |
int |
Dias desde o vencimento mais antigo em aberto |
subscription_status |
string |
Status da assinatura |
amount |
int |
Asaas: soma das cobrancas vencidas em aberto; demais: valor do plano (centavos) |
open_charges |
int |
Cobrancas vencidas em aberto nesta linha |
oldest_due_date |
string |
Vencimento mais antigo em aberto (YYYY-MM-DD) |
Uso no frontend (tabela)¶
// Tabela com colunas:
// | Cliente | Email | Plano | Gateway | Falhas | Dias Atraso | Valor | Status |
//
// A API ja ordena: mais dias de atraso, depois maior valor
// Badge vermelho se days_overdue > 7
// Badge amarelo se payment_failures >= 3
4. GET /admin/finance/churn¶
Analise de churn mensal com breakdown por motivo.
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
months |
int |
6 |
Meses de calendario de historico (1-24) |
start |
YYYY-MM-DD |
— | Inicio do intervalo; vence months |
end |
YYYY-MM-DD |
— | Fim do intervalo (inclusivo) |
gateway |
string |
— | asaas, stripe, pagarme ou pagseguro (legado, 1 registro) |
payment_method |
string |
— | credit_card, boleto ou pix |
Com start/end, a serie continua em buckets mensais, mas o bucket das pontas e
recortado ao intervalo: 01/09 a 15/09 devolve a quinzena, nao setembro
inteiro. Cada bucket declara o trecho que cobriu em period_start e period_end
(este ultimo exclusivo) — sem isso o painel leria "2026-09" como o mes todo.
Formula (ADR-0170) — a mesma do overview.churn_rate/churn_rate_30d e do forecast:
cancelled= assinaturascancelledcomcancelled_atno bucket (updated_atso para historico semcancelled_at).active_start= comecaram antes do bucket e estaoactive, oususpendedcomupdated_atdepois do inicio, ou cancelaram (cancelled_at) depois do inicio.churn_rate = cancelled / active_start x 100(1 casa).overview.active_subscriptionsnao e essa base: sao os pagantes de agora (ADR-0135).
Response (200 OK)¶
{
"monthly": [
{
"month": "2026-01",
"active_start": 33,
"new": 7,
"cancelled": 0,
"active_end": 40,
"churn_rate": 0.0
},
{
"month": "2026-02",
"active_start": 39,
"new": 85,
"cancelled": 3,
"active_end": 121,
"churn_rate": 7.7
},
{
"month": "2026-03",
"active_start": 110,
"new": 122,
"cancelled": 8,
"active_end": 224,
"churn_rate": 7.3
}
],
"by_reason": {
"payment_failure": 0,
"customer_cancelled": 11,
"admin_cancelled": 0,
"chargeback": 0,
"expired": 2
}
}
Uso no frontend (graficos)¶
// Grafico de area: Evolucao de assinantes
// X: monthly[].month
// Y: monthly[].active_end
// Grafico de barras: New vs Cancelled por mes
// X: monthly[].month
// Barra verde: monthly[].new
// Barra vermelha: monthly[].cancelled
// Grafico de pizza: Motivos de cancelamento
// Fatias: by_reason (payment_failure, customer_cancelled, admin_cancelled, chargeback, expired)
5. GET /admin/finance/export¶
Exporta dados financeiros consolidados em xlsx (default), csv (zip multi-aba) ou json. Padrão visual definido em ADR-059 (título + metadados + freeze + autofilter + zebra + auto-width + formatos numéricos nativos).
Duas formas de autenticacao (para compatibilidade com download no browser):
# Opcao 1: Header (padrao, via fetch/http)
GET /api/v1/admin/finance/export?start=2026-01-01&end=2026-03-27
Authorization: Bearer <admin_token>
# Opcao 2: Query param (para window.open / <a href> no browser)
GET /api/v1/admin/finance/export?token=<jwt>&start=2026-01-01&end=2026-03-27&format=xlsx
Query Parameters¶
| Parametro | Tipo | Default | Descricao |
|---|---|---|---|
format |
string |
xlsx |
xlsx (multi-aba), csv (zip), json (aninhado) |
start |
string |
90 dias atras | Data inicio (YYYY-MM-DD) |
end |
string |
hoje | Data fim (YYYY-MM-DD) |
token |
string |
- | JWT token (alternativa ao header, para download no browser) |
Response¶
format |
Content-Type | Filename |
|---|---|---|
xlsx |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
fdplay_financeiro_<start>_<end>.xlsx |
csv |
application/zip (multi-aba) |
fdplay_financeiro_<start>_<end>.zip |
json |
application/json; charset=utf-8 |
fdplay_financeiro_<start>_<end>.json |
Abas do Excel¶
| Aba | Conteudo | Colunas |
|---|---|---|
| Resumo | Recebido, estornado, chargebacks e liquido no periodo (razao de pagamentos, end inclusivo) + MRR, ARR, ativos, clientes, inadimplentes. Valores em 66.844,10 |
Metrica, Valor |
| Assinaturas | Todas as subscriptions no periodo | ID, Cliente, Email, Plano, Gateway, Metodo, Status, Valor, Inicio, Expiracao, Ultimo Pagamento, Falhas, Reembolsado |
| Inadimplentes | Customers com payment_failures > 0 | Cliente, Email, Plano, Gateway, Metodo, Falhas, Ultimo Pagamento, Prox. Cobranca, Dias Atraso, Valor, Status |
| Cancelamentos | Subscriptions canceladas no periodo | Cliente, Email, Plano, Gateway, Data Cancelamento, Valor |
| Reembolsos | Eventos de refund/chargeback no periodo | Evento, Tipo, Subscription, Admin, Motivo, Data |
Uso no frontend¶
// Botao "Exportar Excel" que faz download direto
Future<void> exportFinance(String start, String end) async {
// Token via query param — funciona com window.open no browser
final url =
'$baseUrl/api/v1/admin/finance/export?token=$token&start=$start&end=$end';
// Web: window.open dispara download nativo do browser
if (kIsWeb) {
html.window.open(url, '_blank');
return;
}
// Mobile: salvar via dio + path_provider
final response = await dio.get(
url,
options: Options(responseType: ResponseType.bytes),
);
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/fdplay_financeiro.xlsx');
await file.writeAsBytes(response.data);
// Abrir com app nativo
OpenFile.open(file.path);
}
Integracao Flutter/Dart¶
Service completo¶
import 'dart:convert';
import 'package:http/http.dart' as http;
class FinanceService {
final String baseUrl;
final String token;
FinanceService({required this.baseUrl, required this.token});
Map<String, String> get _headers => {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
/// KPIs consolidados
Future<Map<String, dynamic>> getOverview() async {
final r = await http.get(
Uri.parse('$baseUrl/api/v1/admin/finance/overview'),
headers: _headers,
);
return jsonDecode(r.body);
}
/// Serie temporal de receita
Future<Map<String, dynamic>> getRevenue({
String period = 'monthly',
String? start,
String? end,
}) async {
final params = <String, String>{'period': period};
if (start != null) params['start'] = start;
if (end != null) params['end'] = end;
final uri = Uri.parse('$baseUrl/api/v1/admin/finance/revenue')
.replace(queryParameters: params);
final r = await http.get(uri, headers: _headers);
return jsonDecode(r.body);
}
/// Clientes inadimplentes
Future<Map<String, dynamic>> getOverdue({int page = 0, int limit = 20}) async {
final uri = Uri.parse(
'$baseUrl/api/v1/admin/finance/overdue?page=$page&limit=$limit',
);
final r = await http.get(uri, headers: _headers);
return jsonDecode(r.body);
}
/// Analise de churn
Future<Map<String, dynamic>> getChurn({int months = 6}) async {
final uri = Uri.parse(
'$baseUrl/api/v1/admin/finance/churn?months=$months',
);
final r = await http.get(uri, headers: _headers);
return jsonDecode(r.body);
}
/// URL para download do Excel (com token na query p/ browser)
String getExportUrl({String? start, String? end}) {
final params = <String, String>{'token': token};
if (start != null) params['start'] = start;
if (end != null) params['end'] = end;
return Uri.parse('$baseUrl/api/v1/admin/finance/export')
.replace(queryParameters: params)
.toString();
}
}
Sugestao de Layout do Dashboard¶
+---------------------------------------------+
| MRR | ARR | Ticket Medio |
| R$ 3.840,70 | R$ 46.088 | R$ 19,90 |
+---------------------------------------------+
| Ativos | Inadimpl. | Churn 30d |
| 193 | 34 (!) | 4.0% |
+---------------------------------------------+
[Grafico Linha: Receita Mensal - Bruto vs Liquido]
[Grafico Barras: Stripe vs Asaas por mes]
+---------------------------------------------+
| [Grafico Pizza: | [Grafico Barras: |
| Metodos Pagamento] | Novos vs Cancela] |
+---------------------------------------------+
[Tabela: Inadimplentes - ordenado por falhas]
| Cliente | Email | Falhas | Dias Atraso | Valor |
|---------|-------|--------|-------------|-------|
[Botao: Exportar Excel] [Filtro: Periodo]
Errors¶
| HTTP | Code | Quando |
|---|---|---|
| 401 | UNAUTHORIZED |
Token JWT ausente ou invalido |
| 403 | FORBIDDEN |
Usuario nao e admin |
| 422 | VALIDATION_ERROR |
Parametros de query invalidos (datas, periodo) |
| 422 | — | Parametro de query nao declarado na rota (ADR-0128) |
Notas Tecnicas¶
- Valores em centavos: Todos os campos monetarios (mrr, arr, amount, gross, net) sao em centavos. Frontend converte:
cents / 100para exibir em reais. - Churn rate: Calculado como
(cancelled_no_mes / ativos_inicio_mes) * 100. - Meses de calendario: os buckets de
churn,cohorteforecastandam por mes de calendario. Ate a ADR-0128 andavam de30 * idias, o que repetia ou pulava um mes inteiro numa sequencia de meses de 31 dias. - Overdue (ADR-0134): Asaas = contrato com cobranca vencida em aberto (sem pagamento, exclusao ou estorno depois), uma linha por contrato e, se avulsa, por cliente; demais gateways = venceu ha >=1 dia e status
active/pending/suspended. Ate 14/09/2026 a Asaas tambem era pelonext_billing_date: 1.608 no painel x 1.240 na Asaas. - Dia de Brasilia (ADR-0137):
start/endsao dias deAmerica/Sao_Paulo(start= 03:00 UTC;endinclusivo); buckets diarios/mensais eapplied_filterstambem. Ate 14/09/2026 eram dias UTC. - Sincronizacao (ADR-0138): o painel le a Asaas pelo
webhook_logs; o que o webhook nao entrega (cobranca removida, chargeback, contrato encerrado) chega pela sincronizacao diaria das 03:00. - Saldo x fluxo:
mrr,arr,active_mrrsao saldo (estado da base);gross,net,net_revenue,paid_centssao fluxo, lidos do razao de pagamentos (webhook_logs, ADR-0132). - Export: Gera
.xlsxcom openpyxl. Headers estilizados (fundo escuro, fonte branca). Colunas auto-dimensionadas. - Performance: Overview faz ~5 queries MongoDB. Revenue itera subscriptions em memoria (ok para <10k subs). Para escala maior, migrar para aggregation pipeline.