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 OTP
  • PaymentProvider.moovCI - Moov Money (Côte d'Ivoire)
  • PaymentProvider.waveCI - Wave Money (Côte d'Ivoire)
  • PaymentProvider.cash - Cash payment
  • PaymentProvider.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');
}

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 pending
  • TransactionStatus.confirmed - Payment confirmed
  • TransactionStatus.cancelled - Payment cancelled
  • TransactionStatus.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 client
  • hive_ce: ^2.20.0 - Local storage
  • hive_ce_flutter: ^2.3.4 - Flutter integration for Hive
  • connectivity_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) Testing
  • Credit Card Implementation
  • Enhanced IAP Response Handling
  • Duticotac 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