Duticotac API Flutter
A Flutter package for integrating Duticotac payment services into your Flutter applications. This package provides a simple and efficient way to handle mobile money payments, transaction management, and offer/product fetching.
Features
- 💳 Mobile Money Payments - Support for multiple payment providers (Orange Money, MTN, Moov, Wave, Cash)
- 📊 Transaction Management - Real-time transaction status polling with automatic retry logic
- 🛍️ Offer Management - Fetch and cache product offers with local storage
- 🔄 Offline Support - Built-in caching with Hive for offline access
- 🌐 Network Resilience - Automatic retry for transient network errors
- 📱 Cross-Platform - Works on both iOS and Android
Installation
Add the package, plus hive_ce_flutter (needed to call Hive.initFlutter()
and open the cache box — it is not re-exported):
flutter pub add duticotac_api_flutter hive_ce_flutter
or in your pubspec.yaml:
dependencies:
duticotac_api_flutter: ^0.1.1
hive_ce_flutter: ^2.3.4
Requires Dart ^3.13.0 and Flutter >=3.47.0.
Setup
1. Initialize Hive Adapters
Before using the package, you need to initialize Hive adapters in your app's main function:
import 'package:duticotac_api_flutter/duticotac_api_flutter.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive
await Hive.initFlutter();
// Initialize Duticotac Hive adapters
await initDuticotacHiveAdapters();
runApp(MyApp());
}
2. Initialize the Duticotac Client
final duticotac = Duticotac(
apiKey: 'your-api-key-here',
// baseUrl is optional, defaults to 'https://api.applite.freddydro.dev'
baseUrl: 'https://api.applite.freddydro.dev',
);
Usage
Mobile Money Payments
Process mobile money payments using various payment providers:
import 'package:duticotac_api_flutter/duticotac_api_flutter.dart';
// Process a payment
try {
final response = await duticotac.mobileMoney.cashout(
amount: 5000.0,
transactionId: 'unique-transaction-id',
phone: '+2250123456789',
name: 'John Doe',
email: 'john.doe@example.com',
paymentMethod: PaymentProvider.mtnCI, // or orangeMoneyCI, moovCI, waveCI, cash
productReference: 'product-ref-123', // or use idFromClient
// idFromClient: 'client-id-123', // Alternative to productReference
otp: '123456', // Required for Orange Money CI
kolaboReference: 'kolabo-partner-code', // Optional: for partner rewards
// app: CoreApp.free, // Optional, defaults to CoreApp.free
);
if (response.success && response.data != null) {
final transaction = response.data!;
print('Payment initiated: ${transaction.id}');
print('Status: ${transaction.status}');
// Wave (and any redirect-based provider) returns a URL the customer
// must open to approve the payment. Then poll with getStatus().
if (transaction.paymentUrl != null) {
// launchUrl(Uri.parse(transaction.paymentUrl!));
}
}
} catch (e) {
// A String error code (see Error Handling) or a DioException
print('Payment error: $e');
}
cashout requires either productReference or idFromClient, and an otp
when paymentMethod is PaymentProvider.orangeMoneyCI.
Supported Payment Providers
PaymentProvider.mtnCI- MTN Mobile Money (Côte d'Ivoire)PaymentProvider.orangeMoneyCI- Orange Money (Côte d'Ivoire) - Requires OTPPaymentProvider.moovCI- Moov Money (Côte d'Ivoire)PaymentProvider.waveCI- Wave Money (Côte d'Ivoire)PaymentProvider.cash- Cash paymentPaymentProvider.creditCard- Credit card (coming soon)PaymentProvider.iap- In-App Purchase (coming soon)
Transaction Status
Get Transaction Status by ID
try {
final response = await duticotac.transaction.getStatusById(
id: 'transaction-id-here',
);
if (response.success && response.data != null) {
final transaction = response.data!;
print('Transaction ID: ${transaction.id}');
print('Status: ${transaction.status}');
print('Amount: ${transaction.amount}');
}
} catch (e) {
print('Error: $e');
}
Poll Transaction Status (Recommended)
The getStatus method polls the transaction status until it is confirmed,
cancelled, failed, or the timeout is reached. It does not throw for these
outcomes: only a confirmed transaction comes back with success: true; every
other outcome comes back with success: false and an error code in
response.error.
try {
final response = await duticotac.transaction.getStatus(
'transaction-id-here',
intervalMin: Duration(seconds: 2), // Minimum polling interval
timeout: Duration(seconds: 90), // Maximum polling duration
);
if (response.success) {
print('✅ Payment confirmed: ${response.data!.id}');
} else {
switch (response.error) {
case 'payment-cancelled':
print('❌ Payment cancelled');
case 'payment-failed':
print('❌ Payment failed');
case 'polling-timeout':
print('⏳ Still pending after the timeout');
default:
print('Error: ${response.error}');
}
}
} catch (e) {
print('Error: $e');
}
Features:
- Automatic retry on network errors
- Connectivity checking before retries
- Exponential backoff strategy
- Handles DNS issues after app resume
Offer Management
Fetch and cache product offers:
import 'package:hive_ce_flutter/hive_flutter.dart';
// Open a Hive box for caching (after initDuticotacHiveAdapters())
final box = await Hive.openBox('duticotac');
try {
final offer = await duticotac.offer.get(
reference: 'product-reference-here',
localDB: box,
);
print('Offer Name: ${offer.name}');
print('Price: ${offer.price}'); // int
print('Description: ${offer.description}');
} catch (e) {
print('Error fetching offer: $e');
}
The offer is cached in localDB. On later calls the cached offer is returned
immediately and refreshed in the background. If no offer is found, get throws
the error returned by the API, or 'product-not-found'.
Known issue: the background refresh is not awaited or caught, so if it fails the error surfaces as an unhandled async error in your app's zone.
Models
TransactionModel
class TransactionModel {
final String id;
final String ref;
final String? productId;
final String? offerId;
final String? idFromClient;
final String token;
final PaymentProvider provider;
final double amount;
final double fees;
final PlatformType platform;
final TransactionStatus status;
final String appId;
final String customerId;
final String currency; // e.g. "XOF"
final DateTime createdAt;
final DateTime updatedAt;
final String? paymentUrl; // set for redirect-based providers such as Wave
}
TransactionStatus Enum
TransactionStatus.pending- Payment is pendingTransactionStatus.confirmed- Payment confirmedTransactionStatus.cancelled- Payment cancelledTransactionStatus.failed- Payment failed
PaymentProvider Enum
See Supported Payment Providers section above.
Error Handling
Errors are plain String codes. cashout, getStatusById and offer.get
throw them (along with any DioException from the network), while
getStatus returns them in response.error (see above).
try {
await duticotac.mobileMoney.cashout(...);
} catch (e) {
if (e == 'otp-required') {
// Orange Money needs an OTP
} else if (e == 'ref-or-idFromClient-required') {
// Pass productReference or idFromClient
} else if (e is String) {
// Error code returned by the API, e.g. 'payment-method-not-activated'
print(errors[e] ?? e);
} else {
print('Unknown error: $e');
}
}
The exported errors map gives a French message for each known code, and
getProviderName / getProviderLogo give a display name and logo URL for a
PaymentProvider.
Dependencies
This package requires the following dependencies:
dio: ^5.11.1- HTTP clienthive_ce: ^2.20.0- Local storagehive_ce_flutter: ^2.3.4- Flutter integration for Hiveconnectivity_plus: ^7.3.1- Network connectivity checking
Minimum requirements: Dart SDK ^3.13.0, Flutter >=3.47.0.
Troubleshooting
HiveError: Cannot read, unknown typeId
Call initDuticotacHiveAdapters() after Hive.initFlutter() and before opening
the box you pass to offer.get. The package uses Hive type IDs 50000–50008;
make sure your own adapters don't reuse them.
Roadmap
IAP (In-App Purchase) TestingCredit Card ImplementationEnhanced IAP Response HandlingDuticotac Core API Integration
Testing
flutter test
The suite runs offline: Dio's transport and the connectivity plugin are both
replaced with fakes, and the offer cache uses a temporary Hive box. Test
helpers live in test/support/.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Please run flutter analyze and flutter test before opening a Pull Request.
After changing a Hive model, regenerate the adapters:
dart run build_runner build --delete-conflicting-outputs
License
See LICENSE file for details.
Support
- Issues: GitHub Issues
- Repository: GitHub Repository