Subscription Management
A complete guide to managing customer subscriptions with the MindPaystack SDK, including creation, status management, and customer self-service features.
Overview
This example demonstrates the complete subscription lifecycle:
- ✅ Creating subscriptions for customers
- ✅ Listing and filtering subscriptions
- ✅ Fetching subscription details
- ✅ Enabling and disabling subscriptions
- ✅ Generating customer update links
- ✅ Sending update links via email
Prerequisites
Before managing subscriptions, you’ll need:
- Plans: Create subscription plans first
- Customers: Have customer records in Paystack
- Authorization: Payment method authorization for billing
// Set up your environment
export PAYSTACK_PUBLIC_KEY=pk_test_your_public_key
export PAYSTACK_SECRET_KEY=sk_test_your_secret_key
Complete Subscription Management Example
import 'package:mind_paystack/mind_paystack.dart';
class SubscriptionManager {
static Future<void> initialize() async {
await MindPaystack.initialize(
PaystackConfig(
publicKey: 'pk_test_your_key',
secretKey: 'sk_test_your_key',
environment: Environment.test,
),
);
}
/// Creates a new subscription for a customer
static Future<Subscription?> createSubscription({
required String customerCode,
required String planCode,
required String authorizationCode,
DateTime? startDate,
int quantity = 1,
Map<String, dynamic>? metadata,
}) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.create(
CreateSubscriptionOptions(
customer: customerCode,
plan: planCode,
authorization: authorizationCode,
startDate: startDate,
quantity: quantity,
metadata: metadata,
),
);
if (result.isSuccess) {
final subscription = result.data!;
print('✅ Subscription created successfully!');
print(' Code: ${subscription.subscriptionCode}');
print(' Status: ${subscription.status}');
print(' Customer: ${subscription.customer?.email}');
print(' Plan: ${subscription.plan?.name}');
print(' Next Payment: ${subscription.nextPaymentDate}');
return subscription;
} else {
print('❌ Subscription creation failed: ${result.message}');
return null;
}
} on MindException catch (e) {
print('❌ Error creating subscription: ${e.message}');
return null;
}
}
/// Lists subscriptions with filtering options
static Future<List<Subscription>> listSubscriptions({
String? customerCode,
String? planCode,
String? status,
int perPage = 50,
int page = 1,
}) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.list(
ListSubscriptionsOptions(
customer: customerCode,
plan: planCode,
status: status,
perPage: perPage,
page: page,
),
);
if (result.isSuccess) {
final subscriptions = result.data!;
print('📋 Found ${subscriptions.length} subscriptions');
for (final sub in subscriptions) {
print(' ${sub.subscriptionCode}: ${sub.customer?.email} - ${sub.status}');
}
return subscriptions;
} else {
print('❌ Failed to list subscriptions: ${result.message}');
return [];
}
} on MindException catch (e) {
print('❌ Error listing subscriptions: ${e.message}');
return [];
}
}
/// Fetches detailed subscription information
static Future<Subscription?> getSubscriptionDetails(String subscriptionCode) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.fetch(subscriptionCode);
if (result.isSuccess) {
final subscription = result.data!;
print('📊 Subscription Details:');
print(' Code: ${subscription.subscriptionCode}');
print(' Status: ${subscription.status}');
print(' Customer: ${subscription.customer?.email}');
print(' Plan: ${subscription.plan?.name}');
print(' Amount: ₦${(subscription.amount! / 100).toStringAsFixed(2)}');
print(' Created: ${subscription.createdAt}');
print(' Next Payment: ${subscription.nextPaymentDate}');
// Payment method details
final auth = subscription.authorization;
if (auth != null) {
print(' Payment Method: **** **** **** ${auth.last4}');
print(' Bank: ${auth.bank}');
print(' Card Type: ${auth.cardType}');
}
return subscription;
} else {
print('❌ Subscription not found: ${result.message}');
return null;
}
} on MindException catch (e) {
print('❌ Error fetching subscription: ${e.message}');
return null;
}
}
/// Enables a disabled subscription
static Future<bool> enableSubscription(String subscriptionCode, {String? token}) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.enable(
subscriptionCode,
token != null ? EnableSubscriptionOptions(token: token) : null,
);
if (result.isSuccess) {
final subscription = result.data!;
print('✅ Subscription enabled successfully!');
print(' Status: ${subscription.status}');
return true;
} else {
print('❌ Failed to enable subscription: ${result.message}');
return false;
}
} on MindException catch (e) {
print('❌ Error enabling subscription: ${e.message}');
return false;
}
}
/// Disables an active subscription
static Future<bool> disableSubscription(String subscriptionCode, {String? token}) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.disable(
subscriptionCode,
token != null ? DisableSubscriptionOptions(token: token) : null,
);
if (result.isSuccess) {
final subscription = result.data!;
print('⏸️ Subscription disabled successfully!');
print(' Status: ${subscription.status}');
return true;
} else {
print('❌ Failed to disable subscription: ${result.message}');
return false;
}
} on MindException catch (e) {
print('❌ Error disabling subscription: ${e.message}');
return false;
}
}
/// Generates a secure update link for customers
static Future<String?> generateCustomerUpdateLink(String subscriptionCode) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.generateUpdateLink(subscriptionCode);
if (result.isSuccess) {
final link = result.data!;
print('🔗 Update link generated successfully!');
print(' Link: ${link.link}');
return link.link;
} else {
print('❌ Failed to generate update link: ${result.message}');
return null;
}
} on MindException catch (e) {
print('❌ Error generating update link: ${e.message}');
return null;
}
}
/// Sends update link to customer's email
static Future<bool> sendUpdateLinkToCustomer(String subscriptionCode) async {
final sdk = MindPaystack.instance;
try {
final result = await sdk.subscription.sendUpdateLink(subscriptionCode);
if (result.isSuccess) {
print('📧 Update link sent to customer email successfully!');
return true;
} else {
print('❌ Failed to send update link: ${result.message}');
return false;
}
} on MindException catch (e) {
print('❌ Error sending update link: ${e.message}');
return false;
}
}
}
/// Main function demonstrating complete subscription management workflow
Future<void> main() async {
print('🚀 Starting Subscription Management Demo...\n');
// Initialize SDK
await SubscriptionManager.initialize();
print('✅ SDK initialized successfully!\n');
try {
// 1. Create a subscription
print('1️⃣ Creating a subscription...');
final subscription = await SubscriptionManager.createSubscription(
customerCode: 'CUS_example123',
planCode: 'PLN_premium_monthly',
authorizationCode: 'AUTH_example456',
quantity: 1,
metadata: {
'source': 'web_app',
'campaign': 'summer_promo',
},
);
if (subscription == null) {
print('❌ Could not create subscription. Exiting demo.');
return;
}
print('');
// 2. List subscriptions
print('2️⃣ Listing active subscriptions...');
final activeSubscriptions = await SubscriptionManager.listSubscriptions(
status: 'active',
perPage: 10,
);
print('');
// 3. Get subscription details
print('3️⃣ Fetching subscription details...');
final details = await SubscriptionManager.getSubscriptionDetails(
subscription.subscriptionCode!,
);
print('');
// 4. Generate customer update link
print('4️⃣ Generating customer update link...');
final updateLink = await SubscriptionManager.generateCustomerUpdateLink(
subscription.subscriptionCode!,
);
print('');
// 5. Send update link via email
print('5️⃣ Sending update link to customer...');
final emailSent = await SubscriptionManager.sendUpdateLinkToCustomer(
subscription.subscriptionCode!,
);
print('');
// 6. Temporarily disable subscription
print('6️⃣ Temporarily disabling subscription...');
final disabled = await SubscriptionManager.disableSubscription(
subscription.subscriptionCode!,
);
print('');
// 7. Re-enable subscription
if (disabled) {
print('7️⃣ Re-enabling subscription...');
final enabled = await SubscriptionManager.enableSubscription(
subscription.subscriptionCode!,
);
print('');
}
print('✅ Subscription management demo completed successfully!');
} catch (e) {
print('❌ Demo failed with error: $e');
}
}
/// Helper class for handling subscription webhooks
class SubscriptionWebhookHandler {
/// Processes subscription webhook events
static Future<void> handleWebhookEvent(Map<String, dynamic> payload) async {
final event = payload['event'] as String;
final data = payload['data'] as Map<String, dynamic>;
switch (event) {
case 'subscription.create':
await handleSubscriptionCreated(data);
break;
case 'subscription.disable':
await handleSubscriptionDisabled(data);
break;
case 'subscription.enable':
await handleSubscriptionEnabled(data);
break;
case 'invoice.create':
await handleInvoiceCreated(data);
break;
case 'invoice.update':
await handleInvoiceUpdated(data);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(data);
break;
case 'subscription.not_renew':
await handleSubscriptionExpired(data);
break;
default:
print('🔔 Unhandled webhook event: $event');
}
}
static Future<void> handleSubscriptionCreated(Map<String, dynamic> data) async {
final subscriptionCode = data['subscription_code'];
print('🎉 New subscription created: $subscriptionCode');
// Add your business logic here:
// - Send welcome email
// - Activate customer features
// - Update customer status
}
static Future<void> handleSubscriptionDisabled(Map<String, dynamic> data) async {
final subscriptionCode = data['subscription_code'];
print('⏸️ Subscription disabled: $subscriptionCode');
// Add your business logic here:
// - Pause customer access
// - Send pause notification
// - Update customer status
}
static Future<void> handleSubscriptionEnabled(Map<String, dynamic> data) async {
final subscriptionCode = data['subscription_code'];
print('▶️ Subscription enabled: $subscriptionCode');
// Add your business logic here:
// - Restore customer access
// - Send resume notification
// - Update customer status
}
static Future<void> handleInvoiceCreated(Map<String, dynamic> data) async {
final invoiceCode = data['invoice_code'];
print('📄 New invoice created: $invoiceCode');
// Add your business logic here:
// - Send invoice notification
// - Update billing records
}
static Future<void> handlePaymentFailed(Map<String, dynamic> data) async {
final subscriptionCode = data['subscription']['subscription_code'];
print('❌ Payment failed for subscription: $subscriptionCode');
// Add your business logic here:
// - Send payment failure notification
// - Generate retry schedule
// - Send update link to customer
await SubscriptionManager.sendUpdateLinkToCustomer(subscriptionCode);
}
static Future<void> handleInvoiceUpdated(Map<String, dynamic> data) async {
final invoiceCode = data['invoice_code'];
final status = data['status'];
print('📄 Invoice updated: $invoiceCode - Status: $status');
}
static Future<void> handleSubscriptionExpired(Map<String, dynamic> data) async {
final subscriptionCode = data['subscription_code'];
print('⏰ Subscription expired: $subscriptionCode');
// Add your business logic here:
// - Send expiration notice
// - Offer renewal options
// - Archive customer data
}
}
Key Features Demonstrated
1. Subscription Creation
- Creating subscriptions with existing customers
- Setting start dates and quantities
- Adding custom metadata
2. Subscription Listing & Filtering
- Fetching all subscriptions
- Filtering by customer, plan, and status
- Pagination support
3. Subscription Management
- Enabling and disabling subscriptions
- Fetching detailed subscription information
- Status monitoring
4. Customer Self-Service
- Generating secure update links
- Sending update links via email
- Token-based operations
5. Error Handling
- Comprehensive try-catch blocks
- MindException handling
- Graceful fallbacks
Running the Example
-
Set Environment Variables:
export PAYSTACK_PUBLIC_KEY=pk_test_your_key export PAYSTACK_SECRET_KEY=sk_test_your_key
-
Update Customer/Plan Codes:
// Replace with your actual codes customerCode: 'CUS_your_customer_code', planCode: 'PLN_your_plan_code', authorizationCode: 'AUTH_your_auth_code',
-
Run the Demo:
dart run subscription_management_demo.dart
Production Considerations
Webhook Handling
// Set up webhook endpoint to handle subscription events
app.post('/webhook/subscription', (req, res) async {
final payload = req.body;
await SubscriptionWebhookHandler.handleWebhookEvent(payload);
res.status(200).send('OK');
});
Error Recovery
// Implement retry logic for failed operations
Future<bool> retrySubscriptionOperation(
Future<bool> Function() operation,
{int maxRetries = 3}
) async {
for (int i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (e) {
if (i == maxRetries - 1) rethrow;
await Future.delayed(Duration(seconds: pow(2, i).toInt()));
}
}
return false;
}
Customer Communication
// Send notifications for subscription events
class SubscriptionNotifier {
static Future<void> sendWelcomeEmail(String email, String planName) async {
// Implementation
}
static Future<void> sendPaymentFailedEmail(String email) async {
// Implementation
}
static Future<void> sendUpdateLinkEmail(String email, String link) async {
// Implementation
}
}
Next Steps
- Integrate Webhooks: Set up webhook handling for real-time updates
- Add Customer Portal: Create a self-service portal using update links
- Monitor Subscription Health: Track failed payments and churn
- Implement Retry Logic: Handle payment failures gracefully
- Add Analytics: Track subscription metrics and revenue
Resources
- Subscription Guide - Complete subscription documentation
- Plan Guide - Creating subscription plans
- Webhook Documentation - Paystack webhook events
- API Reference - Subscription model properties
Last updated on