duticotac_flutter 0.3.1
duticotac_flutter: ^0.3.1 copied to clipboard
Payment using Duticotac.
Duticotac Flutter #
Ready-made payment screens for Duticotac: mobile money in Côte d'Ivoire (Orange Money, MTN, Moov, Wave) and in-app purchases through the App Store / Google Play.
The package shows the method choice, the operator list and the phone number
form, sends the cashout, waits for the transaction to settle, and returns an
ApiResponseModel<TransactionModel>. It re-exports the whole
duticotac_api_flutter API and PhoneNumber, so one import is enough.
Features #
- Mobile money: Orange Money CI, MTN CI, Moov CI and Wave CI. The phone
number is checked against the operator's prefix (07, 05, 01), Orange Money
asks for the code obtained with
#144*82#, and Wave opens its payment page in the Wave app or browser. - In-app purchase: starts a consumable purchase with
in_app_purchase. - Offers (
payAnOffer): loads an offer configured in the AppLite dashboard and uses its price and payment methods. - Direct payment (
payWithDuticotac): any amount, with the methods you pick.
Installation #
dependencies:
duticotac_flutter: ^0.3.1
flutter_easyloading: ^4.0.2 # the loading and error toasts need EasyLoading.init()
hive_ce_flutter: ^2.3.4 # for Hive.initFlutter()
Requires Dart ^3.13.0 and Flutter >=3.47.0.
Setup #
Initialise Hive and the Duticotac adapters, open a box for the offer cache, and
install EasyLoading in your MaterialApp: the package reports progress and
errors through it, and nothing shows without EasyLoading.init().
import 'package:duticotac_flutter/duticotac_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
await initDuticotacHiveAdapters();
await Hive.openBox('duticotac_imp_db');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: EasyLoading.init(),
home: const HomeScreen(),
);
}
}
Pay an offer #
payAnOffer loads the offer reference (from the Hive box when it is cached,
from the API otherwise), then proposes the payment methods activated in the
app's Duticotac settings:
- only mobile money → the operator list opens directly;
- only in-app purchase and/or credit card → that flow starts directly;
- both → a sheet asks the customer to choose.
final ApiResponseModel<TransactionModel> result = await payAnOffer(
context,
apiKey: 'YOUR_API_KEY',
reference: 'OFFER_REFERENCE',
localDB: Hive.box('duticotac_imp_db'),
// A unique id per payment attempt, e.g. Uuid().v4().
getTransactionId: () async => uuid.v4(),
// Optional
initialPhoneNumber: PhoneNumber.parse('+2250707070707'),
name: 'Client Name',
email: 'client@example.com',
kolaboReference: 'KOLABO_REF',
onTransactionCompleted: (transactionId) {
// Called after a successful mobile money payment.
},
);
if (result.success) {
// result.data is the settled transaction (null for an in-app purchase).
} else {
// result.error explains why, see "Results and errors".
}
If the offer has no price (price == 0), pass amount, otherwise the call
fails with "Cette offre n'a pas de prix : indiquez un montant".
title and description change the text of the method choice sheet
(default: "Payer
Direct payment #
payWithDuticotac charges amount for productReference, with the methods
listed in selectedProviders (by default the four mobile money operators and
the credit card).
final result = await payWithDuticotac(
context,
apiKey: 'YOUR_API_KEY',
amount: 1000,
productReference: 'YOUR_PRODUCT_REF',
getTransactionId: () async => uuid.v4(),
// Optional
title: 'Payer la commande',
description: 'Choisissez un moyen de paiement',
name: 'Client Name',
email: 'client@example.com',
initialPhoneNumber: PhoneNumber.parse('+2250707070707'),
kolaboReference: 'KOLABO_REF',
selectedProviders: [
PaymentProvider.orangeMoneyCI,
PaymentProvider.mtnCI,
PaymentProvider.waveCI,
],
);
The return type is Future<dynamic>: an ApiResponseModel<TransactionModel>
once a method has run, or null when the customer closes the method choice
sheet. payWithDuticotac has no onTransactionCompleted parameter; read the
returned transaction instead.
To sell through the stores, include PaymentProvider.creditCard and pass
isInAppPurchase: true: the "Carte de Crédit" choice then starts an in-app
purchase of the store product productReference.
How the mobile money flow works #
- The customer picks an operator, then enters an Ivorian number from that
operator (Wave accepts 07, 05 and 01).
initialPhoneNumberpre-fills the field when it belongs to the operator; otherwise the operator prefix is shown. - For Orange Money, the customer also enters the 4-digit code received after
dialling
#144*82#. - On "PAYER",
getTransactionIdis called, then the cashout is sent. For Wave, the returnedpaymentUrlopens in the Wave app (or browser) throughurl_launcher. - The screen waits for the transaction status. On success, a toast confirms
the payment,
onTransactionCompletedreceives the id returned bygetTransactionId, and the screen closes with the result. Cancelled, failed or timed-out payments close the screen withsuccess: false.
When name or email is omitted, placeholder values are sent to the API.
In-app purchase and credit card #
- In-app purchase runs for an offer whose settings include
IAP, or withpayWithDuticotac(isInAppPurchase: true). The offer reference /productReferencemust be the store product id. The package only starts the purchase withbuyConsumable: it resolves withsuccess: trueanddata: nullas soon as the store accepts the request. Listen toInAppPurchase.instance.purchaseStreamin your app to verify, deliver andcompletePurchase. - Credit card outside in-app purchase is not implemented yet: the
"Carte de Crédit" choice opens a placeholder screen whose button does
nothing. The "Espèces" (cash) choice, shown when
PaymentProvider.cashis selected, does nothing either.
Results and errors #
Every flow resolves to an ApiResponseModel<TransactionModel> with success,
data and error (except the null case of payWithDuticotac above). The
package does not throw for a payment failure; it shows an EasyLoading error
toast and returns success: false.
| Situation | error |
|---|---|
| A sheet is closed, or the mobile money screen is left, without paying | show-modal-bottom-sheet-error |
| The transaction is cancelled, fails or times out | the code from duticotac_api_flutter: payment-cancelled, payment-failed, polling-timeout |
payAnOffer or the in-app purchase fails with a known error |
its French message, e.g. "Produit non trouvé" |
payAnOffer or the in-app purchase fails otherwise (network…) |
unknown-error |
On the mobile money screen, a missing number, a missing Orange Money code or a rejected cashout keeps the screen open and shows the message as a toast, so the customer can correct it and retry.
Platform setup #
- Android: the
INTERNETpermission. - In-app purchase: create the products in App Store Connect and the Google Play Console, see in_app_purchase.
- Wave: the payment page opens with url_launcher in external mode.
Dependencies note #
phone_form_field stays on ^10.0.18. Version 11 builds its fields with the
standalone material_ui package; those widgets don't recognise the Material
ancestor provided by package:flutter/material.dart, which this package and
apps created with Flutter 3.47 use, and the phone field crashes with "No
Material widget found".
Contributing #
flutter pub get
flutter analyze
flutter test
Tests never reach the network: API calls get the 400 that flutter_test answers
to every request, and provider logos are served a blank image
(test/support/fake_image_http.dart).