Skip to content

Promo Usage Dashboard — Frontend Integration Guide

Endpoints for the admin promo-code usage panel. Shows how promo codes translate into actual subscriptions, payments and discounts — with optional cross-verification against the Asaas gateway.

Prefix: /api/v1/admin/promo-usage Authentication: Bearer token (JWT) + user_type='admin'


Endpoints

Method Endpoint Description
GET /admin/promo-usage/dashboard KPIs + breakdown by code, gateway, payment method
GET /admin/promo-usage/subscriptions Paginated subscription list with optional gateway verification
GET /admin/promo-usage/export Download XLSX report

1. GET /admin/promo-usage/dashboard

Consolidated KPIs for promo code usage in subscriptions. Ready for dashboard cards and charts.

GET /api/v1/admin/promo-usage/dashboard
Authorization: Bearer <admin_token>

Query Params

Param Type Default Description
code string Filter by specific promo code (e.g. ANGELA). Case-insensitive (uppercased internally).
date_from datetime Start date filter (UTC, ISO 8601)
date_until datetime End date filter (UTC, ISO 8601)

Response (200 OK)

{
  "overview": {
    "total_uses": 101,
    "total_paid": 93,
    "total_pending": 7,
    "total_overdue": 0,
    "total_cancelled": 1,
    "total_discount_given": 1454.40,
    "total_revenue_collected": 555.50,
    "total_revenue_full_price": 2009.90
  },
  "by_code": [
    {
      "code": "ANGELA",
      "title": "Desconto no primeiro mês",
      "is_active": true,
      "discount_first_month": 5.50,
      "total_uses": 101,
      "paid": 93,
      "pending": 7,
      "overdue": 0,
      "cancelled": 1,
      "total_discount_given": 1454.40,
      "total_revenue_collected": 555.50
    }
  ],
  "by_gateway": {
    "asaas": {
      "count": 101,
      "total_discount_given": 1454.40
    }
  },
  "by_payment_method": {
    "pix": {
      "count": 76,
      "total_discount_given": 1094.40
    },
    "credit_card": {
      "count": 25,
      "total_discount_given": 360.00
    }
  }
}

Fields — overview

Field Type Description
total_uses int Total subscriptions with discount_applied=true
total_paid int Subscriptions with status='active'
total_pending int Subscriptions with status='pending'
total_overdue int Subscriptions with status='suspended'
total_cancelled int Subscriptions with status='cancelled' or 'expired'
total_discount_given float Sum of discounts given in reais (full_price - collected)
total_revenue_collected float Sum of discounted values charged (reais)
total_revenue_full_price float What would have been charged without discount (reais)

Fields — by_code (sorted by total_uses desc)

Field Type Description
code string Promo code string
title string Promo code display title
is_active bool Whether code is currently active
discount_first_month float? Discounted price in reais (e.g. 5.50 = R$5,50)
total_uses int Number of subscriptions that used this code
paid / pending / overdue / cancelled int Status breakdown
total_discount_given float Sum of discounts for this code (reais)
total_revenue_collected float Sum of discounted values for this code (reais)

Fields — by_gateway / by_payment_method

Field Type Description
count int Number of subscriptions
total_discount_given float Sum of discounts (reais)

Flutter/Dart

class PromoUsageOverview {
  final int totalUses;
  final int totalPaid;
  final int totalPending;
  final int totalOverdue;
  final int totalCancelled;
  final double totalDiscountGiven;
  final double totalRevenueCollected;
  final double totalRevenueFullPrice;

  PromoUsageOverview.fromJson(Map<String, dynamic> json)
      : totalUses = json['total_uses'],
        totalPaid = json['total_paid'],
        totalPending = json['total_pending'],
        totalOverdue = json['total_overdue'],
        totalCancelled = json['total_cancelled'],
        totalDiscountGiven = (json['total_discount_given'] as num).toDouble(),
        totalRevenueCollected = (json['total_revenue_collected'] as num).toDouble(),
        totalRevenueFullPrice = (json['total_revenue_full_price'] as num).toDouble();
}

class PromoUsageByCode {
  final String code;
  final String title;
  final bool isActive;
  final double? discountFirstMonth;
  final int totalUses;
  final int paid;
  final int pending;
  final int overdue;
  final int cancelled;
  final double totalDiscountGiven;
  final double totalRevenueCollected;

  PromoUsageByCode.fromJson(Map<String, dynamic> json)
      : code = json['code'],
        title = json['title'],
        isActive = json['is_active'],
        discountFirstMonth = (json['discount_first_month'] as num?)?.toDouble(),
        totalUses = json['total_uses'],
        paid = json['paid'],
        pending = json['pending'],
        overdue = json['overdue'],
        cancelled = json['cancelled'],
        totalDiscountGiven = (json['total_discount_given'] as num).toDouble(),
        totalRevenueCollected = (json['total_revenue_collected'] as num).toDouble();
}

class PromoUsageDashboard {
  final PromoUsageOverview overview;
  final List<PromoUsageByCode> byCode;
  final Map<String, Map<String, dynamic>> byGateway;
  final Map<String, Map<String, dynamic>> byPaymentMethod;

  PromoUsageDashboard.fromJson(Map<String, dynamic> json)
      : overview = PromoUsageOverview.fromJson(json['overview']),
        byCode = (json['by_code'] as List)
            .map((e) => PromoUsageByCode.fromJson(e))
            .toList(),
        byGateway = Map<String, Map<String, dynamic>>.from(
            (json['by_gateway'] as Map).map(
                (k, v) => MapEntry(k as String, Map<String, dynamic>.from(v)))),
        byPaymentMethod = Map<String, Map<String, dynamic>>.from(
            (json['by_payment_method'] as Map).map(
                (k, v) => MapEntry(k as String, Map<String, dynamic>.from(v))));
}

Future<PromoUsageDashboard> fetchPromoUsageDashboard({
  required String token,
  String? code,
  DateTime? dateFrom,
  DateTime? dateUntil,
}) async {
  final response = await dio.get(
    '/api/v1/admin/promo-usage/dashboard',
    queryParameters: {
      if (code != null) 'code': code,
      if (dateFrom != null) 'date_from': dateFrom.toIso8601String(),
      if (dateUntil != null) 'date_until': dateUntil.toIso8601String(),
    },
    options: Options(headers: {'Authorization': 'Bearer $token'}),
  );
  return PromoUsageDashboard.fromJson(response.data);
}

2. GET /admin/promo-usage/subscriptions

Paginated list of individual subscriptions that used a promo code. With verify_gateway=true, each subscription includes real-time data from the Asaas API (actual value charged, payment status, payment date).

GET /api/v1/admin/promo-usage/subscriptions?code=ANGELA&verify_gateway=true
Authorization: Bearer <admin_token>

Query Params

Param Type Default Description
code string Filter by promo code (case-insensitive)
status string Filter by subscription status (active, pending, suspended, cancelled, expired)
gateway string Filter by gateway (asaas, stripe)
date_from datetime Start date filter (UTC)
date_until datetime End date filter (UTC)
verify_gateway bool false Cross-check with Asaas API for actual payment data
page int 0 Page offset (0-based)
limit int 50 Items per page (max 500)

Performance: verify_gateway

When verify_gateway=true, the API makes one HTTP request to Asaas per subscription. For large result sets, use pagination (limit=20-50) to keep response times reasonable. Without this flag, data comes only from MongoDB (fast).

Response (200 OK)

{
  "total": 101,
  "subscriptions": [
    {
      "subscription_id": "69d851b2ade1886417f3e9bb",
      "customer_id": "69d6f7cab207249b98f3e9c7",
      "customer_name": "Aline Machado Soares dos Santos",
      "username": "alineprof9331@gmail.com",
      "promo_code_used": "ANGELA",
      "gateway": "asaas",
      "payment_method": "pix",
      "status": "active",
      "discount_first_month": 5.50,
      "original_amount_reais": 19.90,
      "discount_given_reais": 14.40,
      "created_at": "2026-04-10T01:26:09",
      "gateway_payment": {
        "gateway_subscription_id": "sub_gs0j1r7kvjj2sgp9",
        "gateway_payment_id": "pay_g2t8j9vi7jdqpwde",
        "gateway_value": 5.50,
        "gateway_status": "RECEIVED",
        "gateway_billing_type": "PIX",
        "gateway_due_date": "2026-04-10",
        "gateway_payment_date": "2026-04-09",
        "gateway_error": null
      }
    }
  ]
}

Fields — subscription item

Field Type Description
subscription_id string MongoDB ObjectId
customer_id string Customer ObjectId
customer_name string Customer full name (from users collection via $lookup)
username string Customer username/email
promo_code_used string Promo code string used
gateway string Payment gateway (asaas or stripe)
payment_method string pix or credit_card
status string Internal subscription status (active, pending, suspended, cancelled, expired)
discount_first_month float? Discounted price in reais (e.g. 5.50)
original_amount_reais float? Original plan price in reais (e.g. 19.90)
discount_given_reais float Discount amount (original - discounted)
created_at string? ISO 8601 datetime
gateway_payment object? null when verify_gateway=false, populated when true

Fields — gateway_payment (when verify_gateway=true)

Field Type Description
gateway_subscription_id string? Asaas subscription ID (sub_xxx)
gateway_payment_id string? Asaas payment ID (pay_xxx)
gateway_value float? Actual value charged by the gateway in reais
gateway_status string? Gateway payment status: RECEIVED, CONFIRMED, PENDING, OVERDUE
gateway_billing_type string? PIX or CREDIT_CARD
gateway_due_date string? Payment due date (YYYY-MM-DD)
gateway_payment_date string? Date customer actually paid (YYYY-MM-DD, null if not yet paid)
gateway_error string? Error message if gateway query failed

Credit card vs PIX verification

  • PIX: Fetches the first (oldest) payment from the Asaas subscription.
  • Credit card with discount: Fetches the standalone first-month payment (stored as first_month_payment_id in MongoDB), since the Asaas subscription is created at full price for future months.

Flutter/Dart

class GatewayPaymentInfo {
  final String? gatewaySubscriptionId;
  final String? gatewayPaymentId;
  final double? gatewayValue;
  final String? gatewayStatus;
  final String? gatewayBillingType;
  final String? gatewayDueDate;
  final String? gatewayPaymentDate;
  final String? gatewayError;

  GatewayPaymentInfo.fromJson(Map<String, dynamic> json)
      : gatewaySubscriptionId = json['gateway_subscription_id'],
        gatewayPaymentId = json['gateway_payment_id'],
        gatewayValue = (json['gateway_value'] as num?)?.toDouble(),
        gatewayStatus = json['gateway_status'],
        gatewayBillingType = json['gateway_billing_type'],
        gatewayDueDate = json['gateway_due_date'],
        gatewayPaymentDate = json['gateway_payment_date'],
        gatewayError = json['gateway_error'];

  /// True if the gateway confirmed the payment was received
  bool get isPaid =>
      gatewayStatus == 'RECEIVED' || gatewayStatus == 'CONFIRMED';

  /// True if the discount was correctly applied at gateway level
  bool get discountVerified => gatewayValue != null && gatewayError == null;
}

class PromoUsageSubscription {
  final String subscriptionId;
  final String customerId;
  final String customerName;
  final String username;
  final String promoCodeUsed;
  final String gateway;
  final String paymentMethod;
  final String status;
  final double? discountFirstMonth;
  final double? originalAmountReais;
  final double discountGivenReais;
  final String? createdAt;
  final GatewayPaymentInfo? gatewayPayment;

  PromoUsageSubscription.fromJson(Map<String, dynamic> json)
      : subscriptionId = json['subscription_id'],
        customerId = json['customer_id'],
        customerName = json['customer_name'],
        username = json['username'],
        promoCodeUsed = json['promo_code_used'],
        gateway = json['gateway'],
        paymentMethod = json['payment_method'],
        status = json['status'],
        discountFirstMonth = (json['discount_first_month'] as num?)?.toDouble(),
        originalAmountReais = (json['original_amount_reais'] as num?)?.toDouble(),
        discountGivenReais = (json['discount_given_reais'] as num).toDouble(),
        createdAt = json['created_at'],
        gatewayPayment = json['gateway_payment'] != null
            ? GatewayPaymentInfo.fromJson(json['gateway_payment'])
            : null;
}

Future<Map<String, dynamic>> fetchPromoUsageSubscriptions({
  required String token,
  String? code,
  String? status,
  String? gateway,
  bool verifyGateway = false,
  int page = 0,
  int limit = 50,
}) async {
  final response = await dio.get(
    '/api/v1/admin/promo-usage/subscriptions',
    queryParameters: {
      if (code != null) 'code': code,
      if (status != null) 'status': status,
      if (gateway != null) 'gateway': gateway,
      'verify_gateway': verifyGateway,
      'page': page,
      'limit': limit,
    },
    options: Options(headers: {'Authorization': 'Bearer $token'}),
  );
  return response.data; // { total, subscriptions: [...] }
}

3. GET /admin/promo-usage/export

Download promo usage report in xlsx (default), csv or json. By default includes gateway verification columns. Style follows ADR-059.

GET /api/v1/admin/promo-usage/export?code=ANGELA&format=xlsx
Authorization: Bearer <admin_token>

Query Params

Param Type Default Description
format string xlsx xlsx / csv / json
code string Filter by promo code
date_from datetime Start date filter (UTC)
date_until datetime End date filter (UTC)
verify_gateway bool true Include gateway verification columns (default ON for exports)
token string Bearer token (alternative to Authorization header — for direct URL downloads)

Response

format Content-Type Filename
xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet promo_usage_<CODE>_<TS>.xlsx
csv text/csv; charset=utf-8 promo_usage_<CODE>_<TS>.csv
json application/json; charset=utf-8 promo_usage_<CODE>_<TS>.json

Sheet — "Uso de Cupons"

Base columns (always present):

Column Description
ID Subscription ObjectId
Cliente Customer full name
Username Customer username/email
Email Customer email
Cupom Promo code used
Gateway asaas or stripe
Pagamento pix or credit_card
Status (interno) Internal subscription status
Valor Original (R$) Plan price before discount (e.g. 19.90)
Valor Cobrado (R$) Discounted price charged (e.g. 5.50)
Desconto (R$) Discount amount (e.g. 14.40)
Criado em DD/MM/YYYY HH:MM

Gateway columns (when verify_gateway=true):

Column Description
Asaas Sub ID Asaas subscription ID (sub_xxx)
Asaas Payment ID Asaas payment ID (pay_xxx)
Valor Gateway (R$) Actual value charged by Asaas
Status Gateway RECEIVED, CONFIRMED, PENDING, OVERDUE
Tipo Cobranca PIX or CREDIT_CARD
Vencimento Payment due date
Data Pagamento Date customer paid (empty if not yet paid)
Erro Gateway Error if Asaas query failed

Flutter/Dart — Download and save

Modo 1 — Dio com header (recomendado):

Future<File> downloadPromoUsageXlsx({
  required String token,
  String? code,
  bool verifyGateway = true,
}) async {
  final response = await Dio().get(
    '$baseUrl/api/v1/admin/promo-usage/export',
    queryParameters: {
      if (code != null) 'code': code,
      'verify_gateway': verifyGateway,
    },
    options: Options(
      headers: {'Authorization': 'Bearer $token'},
      responseType: ResponseType.bytes,
    ),
  );

  final dir = await getApplicationDocumentsDirectory();
  final filename = 'promo_usage_${DateTime.now().millisecondsSinceEpoch}.xlsx';
  final file = File('${dir.path}/$filename');
  await file.writeAsBytes(response.data as List<int>);
  return file;
}

Modo 2 — URL direta com token como query param (para launchUrl, WebView, share link):

Future<void> openPromoUsageXlsx(String token, {String? code}) async {
  final params = <String, String>{'token': token};
  if (code != null) params['code'] = code;
  final uri = Uri.parse('$baseUrl/api/v1/admin/promo-usage/export')
      .replace(queryParameters: params);
  await launchUrl(uri, mode: LaunchMode.externalApplication);
}

UI Suggestions

Dashboard Cards (from /dashboard)

Card Value Color Logic
Total de Usos overview.total_uses
Pagos overview.total_paid Green
Pendentes overview.total_pending Yellow
Cancelados overview.total_cancelled Red
Desconto Concedido R$ overview.total_discount_given
Receita Coletada R$ overview.total_revenue_collected

Filters Bar

  • Dropdown "Cupom": Populate from by_code[].code (or a separate promo codes list endpoint)
  • Date range picker: Maps to date_from / date_until
  • Toggle "Verificar Gateway": Controls verify_gateway param on the table

Data Table (from /subscriptions)

Column Source Notes
Cliente customer_name Primary column
Email username
Cupom promo_code_used
Metodo payment_method Badge: PIX / Cartao
Valor Original original_amount_reais Format as R$ 19,90
Valor Cobrado discount_first_month Format as R$ 5,50
Desconto discount_given_reais Format as R$ 14,40
Status status Badge with color
Valor Gateway gateway_payment.gateway_value Only when verify enabled
Status Gateway gateway_payment.gateway_status Badge: RECEIVED=green, PENDING=yellow, OVERDUE=red
Data Pagamento gateway_payment.gateway_payment_date Only when verify enabled

Export Button

[Download XLSX] → calls /export?code={selected_code}&verify_gateway=true

Notes

  • Revenue in reais (not cents). e.g. 555.50 = R$555,50.
  • Admin authentication required on all endpoints.
  • Status mapping: active = paid, pending = awaiting payment, suspended = overdue, cancelled/expired = cancelled.
  • Gateway status values: Asaas returns RECEIVED (PIX paid), CONFIRMED (credit card confirmed), PENDING (awaiting), OVERDUE (past due).
  • Credit card discount flow: First month is a standalone Asaas payment (not part of the subscription). The subscription starts at full price for month 2+. The gateway_payment field correctly fetches the standalone payment when first_month_payment_id exists.
  • Aggregation source: /dashboard uses MongoDB aggregation only (fast). /subscriptions?verify_gateway=true makes additional HTTP calls to Asaas (slower, use pagination).