Reembolso — Guia de Integracao Frontend (Admin)¶
Endpoint¶
POST /api/v1/subscriptions/{subscription_id}/refund
Authorization: Bearer <admin_token>
Content-Type: application/json
Payload¶
Apenas reason e obrigatorio. O frontend NAO precisa enviar IDs de pagamento — o backend auto-detecta o gateway e busca o ultimo pagamento automaticamente.
Como funciona internamente:
- Stripe: backend le
stripe_customer_idda subscription → lista charges no Stripe → reembolsa o ultimo - Asaas: backend le
asaas_subscription_id→ lista pagamentos no Asaas → reembolsa o ultimo
O frontend so envia o motivo. Nao precisa saber qual gateway e nem buscar IDs.
Campos opcionais (uso interno/debug)
payment_intent_id, payment_id e charge_id existem no schema mas sao para uso interno. O unico cenario onde o frontend precisaria enviar payment_intent_id e se o customer foi deletado antes do reembolso (o backend perde o stripe_customer_id). Na pratica, sempre reembolse antes de deletar o customer.
Deteccao automatica por gateway¶
O backend le o campo gateway da subscription e despacha:
| Gateway | Como o backend reembolsa | Campo opcional |
|---|---|---|
| Stripe (cartao) | Busca ultimo charge via stripe_customer_id → refund |
payment_intent_id (se customer deletado) |
| Asaas (cartao/PIX) | Busca ultimo pagamento via asaas_subscription_id → refund |
payment_id (se quiser reembolsar pagamento especifico) |
Response (200 OK)¶
{
"docs": [{ "...subscription atualizada..." }],
"info": {
"message": "Refund processed successfully",
"subscription_id": "69ca960e...",
"gateway": "stripe",
"refund_type": "stripe_refund",
"refund_amount": 1990,
"gateway_response": {
"id": "re_3TGhhU...",
"amount": 1990,
"currency": "brl",
"status": "succeeded",
"charge": "ch_3TGhhU...",
"payment_intent": "pi_3TGhhU..."
},
"reason": "Motivo informado pelo admin",
"refunded_at": "2026-03-30T15:31:49Z",
"admin": "root"
}
}
Errors¶
| HTTP | Codigo | Cenario |
|---|---|---|
| 404 | NOT_FOUND |
Subscription nao encontrada |
| 422 | REFUND_NOT_ALLOWED |
Status nao permite reembolso |
| 422 | VALIDATION_ERROR |
Customer sem stripe_customer_id (passar payment_intent_id manualmente) |
| 500 | REFUND_FAILED |
Falha no gateway (Stripe/Asaas rejeitou) |
Consultar historico de reembolsos¶
Retorna lista de reembolsos e chargebacks da subscription.
Fluxo recomendado na UI admin¶
1. Admin abre detalhes da subscription
2. Clica "Reembolsar"
3. Modal pede "Motivo" (textarea, min 5 chars)
4. POST /subscriptions/{id}/refund com {"reason": "..."}
5. Exibir resultado:
- Sucesso: "Reembolso de R$ X,XX processado (ID: re_xxx)"
- Erro: exibir message do error response
6. Atualizar dados da subscription (total_refunded mudou)
Dart/Flutter¶
/// Solicitar reembolso de uma subscription (admin only).
///
/// [subscriptionId] — ID da subscription no MongoDB
/// [reason] — Motivo do reembolso (min 5 chars)
/// [paymentIntentId] — Opcional, necessario se customer foi deletado (Stripe)
Future<Map<String, dynamic>> refundSubscription({
required String token,
required String subscriptionId,
required String reason,
String? paymentIntentId,
}) async {
final body = <String, dynamic>{'reason': reason};
if (paymentIntentId != null) body['payment_intent_id'] = paymentIntentId;
final resp = await http.post(
Uri.parse('$baseUrl/api/v1/subscriptions/$subscriptionId/refund'),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(body),
);
final data = jsonDecode(resp.body);
if (resp.statusCode == 200) return data;
throw Exception(data['error']?['message'] ?? 'Erro ao reembolsar');
}
Exibir valor reembolsado¶
/// Converter centavos para reais formatado.
String formatCents(int cents) => 'R\$ ${(cents / 100).toStringAsFixed(2)}';
// Exemplo: formatCents(1990) → "R$ 19,90"
Rotas de Reembolso por Gateway (Admin)¶
Alem da rota generica /subscriptions/{id}/refund, existem rotas gateway-specific que validam o gateway antes de operar:
Stripe¶
| Metodo | Endpoint | Descricao |
|---|---|---|
| POST | /api/v1/admin/stripe/subscriptions/{id}/refund |
Estornar pagamento Stripe |
| GET | /api/v1/admin/stripe/subscriptions/{id}/refunds |
Historico de estornos Stripe |
Request Body (Stripe):
| Campo | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
payment_intent_id |
string |
Nao | Stripe PaymentIntent ID (pi_xxx). Auto-detecta se omitido |
reason |
string |
Sim | Motivo do reembolso (5-500 chars) |
O event_type no audit log e ADMIN.REFUND.STRIPE_REFUND.
Asaas¶
| Metodo | Endpoint | Descricao |
|---|---|---|
| POST | /api/v1/admin/asaas/subscriptions/{id}/refund |
Estornar pagamento Asaas |
| GET | /api/v1/admin/asaas/subscriptions/{id}/refunds |
Historico de estornos Asaas |
Request Body (Asaas):
| Campo | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
payment_id |
string |
Nao | Asaas Payment ID (pay_xxx). Auto-detecta se omitido |
value |
float |
Nao | Valor a estornar em reais (None = reembolso total) |
reason |
string |
Sim | Motivo do reembolso (5-500 chars) |
O event_type no audit log e ADMIN.REFUND.ASAAS_REFUND.
Recomendacao
Prefira usar as rotas gateway-specific (/admin/stripe/... ou /admin/asaas/...) ao inves da rota generica. Elas validam o gateway antes de operar e evitam operacoes acidentais no gateway errado.
Para exemplos completos de request/response, consulte API de Administracao e API de Administracao — Asaas.
Notas¶
- Valores em centavos —
refund_amount: 1990= R$19,90. Frontend converte/ 100 total_refundedna subscription acumula todos os reembolsos (campo em centavos)- Reembolso Stripe demora 5-10 dias uteis para aparecer no cartao do cliente
- Reembolso Asaas PIX e processado em ate 24h pela Asaas
- Audit trail — todas as acoes de reembolso sao registradas em
webhook_logscomevent_typeprefixadoADMIN.REFUND.STRIPE_REFUNDouADMIN.REFUND.ASAAS_REFUND