SubLoop Flutter SDK

Official Flutter SDK for SubLoop's payment analytics API. Easy integration for tracking payments and analytics in Flutter mobile and web apps.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  subloop: ^1.0.0

Then run:

flutter pub get

Quick Start

import 'package:subloop/subloop.dart';

// Initialize the client
final subloop = SubLoop('sk_your_api_key_here');

// Send payment data
final payment = await subloop.payments.createSuccessful(
  'cust_001',    // customerId
  29.99,         // amount
  currency: 'USD',
  subscriptionId: 'sub_001',
);

// Get analytics
final overview = await subloop.analytics.getOverview();
final mrr = await subloop.analytics.getMRR();

Authentication

Get your API key from your SubLoop dashboard:

final subloop = SubLoop('sk_your_api_key_here');

// For development/testing
final subloop = SubLoop('sk_your_api_key_here', baseUrl: 'http://localhost:8000');

Payment Events

Create Payment Event

// Manual payment creation
final payment = await subloop.payments.create({
  'customer_id': 'cust_001',
  'subscription_id': 'sub_001', // Optional for recurring payments
  'amount': 29.99,
  'currency': 'USD',
  'status': 'succeeded', // succeeded, failed, pending, refunded
  'payment_date': DateTime.now().toIso8601String(),
  'payment_method': 'card',
  'metadata': {
    'gateway_transaction_id': 'txn_123',
    'customer_email': '[email protected]',
  },
});

// Helper methods for common scenarios
final successfulPayment = await subloop.payments.createSuccessful(
  'cust_001',
  29.99,
  currency: 'USD',
  subscriptionId: 'sub_001',
);

final failedPayment = await subloop.payments.createFailed(
  'cust_002',
  19.99,
  currency: 'USD',
);

final refundedPayment = await subloop.payments.createRefunded(
  'cust_003',
  39.99,
  currency: 'USD',
  subscriptionId: 'sub_002',
);

final pendingPayment = await subloop.payments.createPending(
  'cust_004',
  49.99,
  currency: 'USD',
);

List Payment Events

// Get all payments
final payments = await subloop.payments.list();

// With pagination
final payments = await subloop.payments.list({'page': '2'});

// Get specific payment
final payment = await subloop.payments.get(123);

Analytics

Dashboard Overview

final overview = await subloop.analytics.getOverview();

print('MRR: \$${overview['mrr']}');
print('Total Revenue (30d): \$${overview['total_revenue_30d']}');
print('Active Subscriptions: ${overview['active_subscriptions']}');
print('Success Rate: ${overview['payment_success_rate']}%');

Monthly Recurring Revenue

final mrr = await subloop.analytics.getMRR();

print('Current MRR: \$${mrr['mrr']}');
print('Previous MRR: \$${mrr['previous_mrr']}');
print('Growth: ${mrr['growth_percentage']}%');

Revenue Analytics

// Get revenue for different periods
final revenue30d = await subloop.analytics.getRevenue(period: '30days');
final revenue7d = await subloop.analytics.getRevenueLast7Days();
final revenue90d = await subloop.analytics.getRevenueLast90Days();

// Custom date range
final customRevenue = await subloop.analytics.getRevenueForDateRange(
  '2025-06-01',
  '2025-07-31',
);

// Current and previous month
final currentMonth = await subloop.analytics.getRevenueCurrentMonth();
final previousMonth = await subloop.analytics.getRevenuePreviousMonth();

Payment Activity

// Get recent payments
final recentPayments = await subloop.analytics.getPayments(limit: 10);

// Get only successful payments
final successfulPayments = await subloop.analytics.getSuccessfulPayments(limit: 20);

// Get only recurring payments
final recurringPayments = await subloop.analytics.getRecurringPayments(limit: 15);

// Filter by status
final failedPayments = await subloop.analytics.getFailedPayments(limit: 10);

Integration Examples

Flutter App with Provider State Management

// lib/services/subloop_service.dart
import 'package:flutter/foundation.dart';
import 'package:subloop/subloop.dart';

class SubLoopService extends ChangeNotifier {
  final SubLoop _subloop;
  Map<String, dynamic>? _analytics;
  bool _loading = false;

  SubLoopService(String apiKey) : _subloop = SubLoop(apiKey);

  Map<String, dynamic>? get analytics => _analytics;
  bool get loading => _loading;

  Future<void> trackPayment({
    required String customerId,
    required double amount,
    required String currency,
    String? subscriptionId,
    Map<String, dynamic>? metadata,
  }) async {
    try {
      await _subloop.payments.createSuccessful(
        customerId,
        amount,
        currency: currency,
        subscriptionId: subscriptionId,
        metadata: metadata,
      );

      // Refresh analytics after tracking payment
      await loadAnalytics();
    } catch (e) {
      debugPrint('Error tracking payment: $e');
      rethrow;
    }
  }

  Future<void> loadAnalytics() async {
    _loading = true;
    notifyListeners();

    try {
      final overview = await _subloop.analytics.getOverview();
      final mrr = await _subloop.analytics.getMRR();
      final recentPayments = await _subloop.analytics.getPayments(limit: 5);

      _analytics = {
        'overview': overview,
        'mrr': mrr,
        'recent_payments': recentPayments,
      };
    } catch (e) {
      debugPrint('Error loading analytics: $e');
    } finally {
      _loading = false;
      notifyListeners();
    }
  }
}
// lib/screens/analytics_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/subloop_service.dart';

class AnalyticsScreen extends StatefulWidget {
  @override
  _AnalyticsScreenState createState() => _AnalyticsScreenState();
}

class _AnalyticsScreenState extends State<AnalyticsScreen> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      context.read<SubLoopService>().loadAnalytics();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Revenue Analytics'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () => context.read<SubLoopService>().loadAnalytics(),
          ),
        ],
      ),
      body: Consumer<SubLoopService>(
        builder: (context, subloopService, child) {
          if (subloopService.loading) {
            return Center(child: CircularProgressIndicator());
          }

          final analytics = subloopService.analytics;
          if (analytics == null) {
            return Center(child: Text('No analytics data available'));
          }

          final overview = analytics['overview'] as Map<String, dynamic>;
          final mrr = analytics['mrr'] as Map<String, dynamic>;
          final recentPayments = analytics['recent_payments'] as Map<String, dynamic>;

          return SingleChildScrollView(
            padding: EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Overview Cards
                Row(
                  children: [
                    Expanded(
                      child: _buildMetricCard(
                        'MRR',
                        '\$${overview['mrr']}',
                        Icons.trending_up,
                        Colors.green,
                      ),
                    ),
                    SizedBox(width: 16),
                    Expanded(
                      child: _buildMetricCard(
                        'Revenue (30d)',
                        '\$${overview['total_revenue_30d']}',
                        Icons.attach_money,
                        Colors.blue,
                      ),
                    ),
                  ],
                ),
                SizedBox(height: 16),
                Row(
                  children: [
                    Expanded(
                      child: _buildMetricCard(
                        'Active Subs',
                        '${overview['active_subscriptions']}',
                        Icons.people,
                        Colors.purple,
                      ),
                    ),
                    SizedBox(width: 16),
                    Expanded(
                      child: _buildMetricCard(
                        'Success Rate',
                        '${overview['payment_success_rate']}%',
                        Icons.check_circle,
                        Colors.orange,
                      ),
                    ),
                  ],
                ),
                SizedBox(height: 24),

                // MRR Growth
                Card(
                  child: Padding(
                    padding: EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'MRR Growth',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                        SizedBox(height: 8),
                        Text('Current: \$${mrr['mrr']}'),
                        Text('Previous: \$${mrr['previous_mrr']}'),
                        Text('Growth: ${mrr['growth_percentage']}%'),
                      ],
                    ),
                  ),
                ),
                SizedBox(height: 16),

                // Recent Payments
                Card(
                  child: Padding(
                    padding: EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Recent Payments',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                        SizedBox(height: 8),
                        ...((recentPayments['data'] as List).map((payment) => 
                          ListTile(
                            title: Text('\$${payment['amount']}'),
                            subtitle: Text('${payment['customer_id']} • ${payment['status']}'),
                            trailing: Text(payment['payment_date']),
                          ),
                        )),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildMetricCard(String title, String value, IconData icon, Color color) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(icon, color: color, size: 20),
                SizedBox(width: 8),
                Text(title, style: TextStyle(fontSize: 14, color: Colors.grey[600])),
              ],
            ),
            SizedBox(height: 8),
            Text(
              value,
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
    );
  }
}

In-App Purchase Integration

// lib/services/purchase_service.dart
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:subloop/subloop.dart';

class PurchaseService {
  final SubLoop _subloop;
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;

  PurchaseService(String apiKey) : _subloop = SubLoop(apiKey);

  Future<void> handlePurchase(PurchaseDetails purchase, String customerId) async {
    if (purchase.status == PurchaseStatus.purchased) {
      try {
        // Track successful purchase in SubLoop
        await _subloop.payments.createSuccessful(
          customerId,
          _getPriceFromProductId(purchase.productID),
          currency: 'USD',
          subscriptionId: _isSubscription(purchase.productID) ? purchase.productID : null,
          metadata: {
            'platform': 'ios', // or 'android'
            'transaction_id': purchase.purchaseID,
            'product_id': purchase.productID,
            'purchase_date': DateTime.now().toIso8601String(),
          },
        );

        // Complete the purchase
        if (purchase.pendingCompletePurchase) {
          await _inAppPurchase.completePurchase(purchase);
        }
      } catch (e) {
        print('Error tracking purchase in SubLoop: $e');
      }
    } else if (purchase.status == PurchaseStatus.error) {
      // Track failed purchase
      await _subloop.payments.createFailed(
        customerId,
        _getPriceFromProductId(purchase.productID),
        currency: 'USD',
        metadata: {
          'platform': 'ios', // or 'android'
          'error': purchase.error?.message,
          'product_id': purchase.productID,
        },
      );
    }
  }

  double _getPriceFromProductId(String productId) {
    // Map product IDs to prices
    final prices = {
      'premium_monthly': 9.99,
      'premium_yearly': 99.99,
      'pro_monthly': 19.99,
      'pro_yearly': 199.99,
    };
    return prices[productId] ?? 0.0;
  }

  bool _isSubscription(String productId) {
    return productId.contains('monthly') || productId.contains('yearly');
  }
}

Stripe Payment Integration

// lib/services/payment_service.dart
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:subloop/subloop.dart';

class PaymentService {
  final SubLoop _subloop;

  PaymentService(String apiKey) : _subloop = SubLoop(apiKey);

  Future<void> processPayment({
    required String customerId,
    required double amount,
    required String currency,
    String? subscriptionId,
  }) async {
    try {
      // Create payment intent with your backend
      final paymentIntent = await _createPaymentIntent(amount, currency);

      // Confirm payment with Stripe
      await Stripe.instance.confirmPayment(
        paymentIntentClientSecret: paymentIntent['client_secret'],
        data: PaymentMethodData(
          billingDetails: BillingDetails(
            email: '[email protected]',
          ),
        ),
      );

      // Track successful payment in SubLoop
      await _subloop.payments.createSuccessful(
        customerId,
        amount,
        currency: currency,
        subscriptionId: subscriptionId,
        metadata: {
          'stripe_payment_intent_id': paymentIntent['id'],
          'payment_method': 'stripe',
        },
      );

      print('Payment successful and tracked in SubLoop');
    } catch (e) {
      // Track failed payment in SubLoop
      await _subloop.payments.createFailed(
        customerId,
        amount,
        currency: currency,
        subscriptionId: subscriptionId,
        metadata: {
          'error': e.toString(),
          'payment_method': 'stripe',
        },
      );

      print('Payment failed: $e');
      rethrow;
    }
  }

  Future<Map<String, dynamic>> _createPaymentIntent(double amount, String currency) async {
    // Call your backend to create payment intent
    // This is just a placeholder - implement your backend call
    throw UnimplementedError('Implement backend call to create payment intent');
  }
}

Error Handling

import 'package:subloop/subloop.dart';

try {
  final payment = await subloop.payments.create({
    'customer_id': 'cust_001',
    'amount': 29.99,
    'currency': 'USD',
    'status': 'succeeded',
    'payment_date': DateTime.now().toIso8601String(),
  });
} on SubLoopException catch (e) {
  print('SubLoop Error: ${e.message}');
  print('Status Code: ${e.getStatusCode()}');

  if (e.isValidationError()) {
    print('Validation errors: ${e.getDetails()}');
  } else if (e.isAuthenticationError()) {
    print('Authentication failed - check your API key');
  } else if (e.isRateLimitError()) {
    print('Rate limit exceeded - please try again later');
  } else if (e.isServerError()) {
    print('Server error - please try again later');
  }
} catch (e) {
  print('General Error: $e');
}

Testing

// test/subloop_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:subloop/subloop.dart';

void main() {
  group('SubLoop SDK Tests', () {
    late SubLoop subloop;

    setUp(() {
      subloop = SubLoop('test_api_key', baseUrl: 'http://localhost:8000');
    });

    test('should initialize with API key', () {
      expect(subloop.apiKey, equals('test_api_key'));
      expect(subloop.baseUrl, equals('http://localhost:8000'));
    });

    test('should throw error for empty API key', () {
      expect(() => SubLoop(''), throwsArgumentError);
    });

    test('should create successful payment', () async {
      // Mock your API responses for testing
      // This is a placeholder - implement proper mocking
    });
  });
}

Requirements

  • Flutter 3.0.0 or higher
  • Dart 2.17.0 or higher

Support

License

MIT License. See LICENSE for more information.