Official Flutter SDK for SubLoop's payment analytics API. Easy integration for tracking payments and analytics in Flutter mobile and web apps.
Add this to your package's pubspec.yaml file:
dependencies:
subloop: ^1.0.0
Then run:
flutter pub get
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();
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');
// 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',
);
// 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);
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']}%');
final mrr = await subloop.analytics.getMRR();
print('Current MRR: \$${mrr['mrr']}');
print('Previous MRR: \$${mrr['previous_mrr']}');
print('Growth: ${mrr['growth_percentage']}%');
// 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();
// 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);
// 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),
),
],
),
),
);
}
}
// 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');
}
}
// 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');
}
}
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');
}
// 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
});
});
}
MIT License. See LICENSE for more information.