Flutter SDK for CinetPay payments. Opens the CinetPay checkout page in a WebView and reports the payment result back to your app.
This is a frontend-only SDK. The payment must be initialized on your backend first (using cinetpay-js, cinetpay-python, cinetpay-go, or any backend), which returns a paymentToken. The Flutter SDK then opens https://secure.cinetpay.net/checkout/{paymentToken} in a WebView.
Add the package to your pubspec.yaml:
dependencies:
cinetpay_flutter: ^1.0.0Then run:
flutter pub getAjoutez la permission internet dans android/app/src/main/AndroidManifest.xml :
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- ... -->
</manifest>Vérifiez que le minSdkVersion est au moins 19 dans android/app/build.gradle :
android {
defaultConfig {
minSdkVersion 19
}
}Ajoutez les schémas d'URL dans ios/Runner/Info.plist pour le fallback navigateur (url_launcher) :
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>Vérifiez que la version minimale est 12.0 dans ios/Podfile :
platform :ios, '12.0'Your backend calls the CinetPay API (POST /v1/payment) and returns a paymentToken to the mobile app. This step is not handled by this SDK.
App --> Your Backend --> CinetPay API
App <-- paymentToken <-- Your Backend
Import the SDK:
import 'package:cinetpay_flutter/cinetpay_flutter.dart';CinetPay.show(
context: context,
paymentToken: 'abc123def456...',
onPaymentSuccess: (data) {
print('Paid ${data.amount} ${data.currency}');
print('Transaction ID: ${data.transactionId}');
},
onPaymentFailed: (data) {
print('Payment refused');
},
onPaymentPending: (data) {
print('Payment pending: ${data.status.value}');
},
onClose: () {
print('Checkout closed');
},
onError: (error) {
print('Error: ${error.code} - ${error.message}');
},
);Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CinetPayCheckoutPage(
paymentToken: 'abc123def456...',
onPaymentSuccess: (data) {
Navigator.pop(context);
// Handle success
},
onPaymentFailed: (data) {
Navigator.pop(context);
// Handle failure
},
onClose: () => Navigator.pop(context),
),
),
);CinetPayButton(
paymentToken: 'abc123def456...',
text: 'Payer 5000 XOF',
onPaymentSuccess: (data) { ... },
onPaymentFailed: (data) { ... },
)With a custom icon:
CinetPayButton(
paymentToken: token,
text: 'Pay now',
icon: Icon(Icons.payment),
onPaymentSuccess: (data) { ... },
)With custom styling:
CinetPayButton(
paymentToken: token,
text: 'Confirm Payment',
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
),
onPaymentSuccess: (data) { ... },
)If WebView is not suitable, you can open the checkout in the system browser. Note that callbacks will NOT work in this mode; rely on your backend webhook instead.
await CinetPay.openInBrowser(
paymentToken: 'abc123def456...',
onError: (error) => print(error.message),
);For reuse, you can create a CheckoutConfig object:
final config = CheckoutConfig(
paymentToken: token,
onPaymentSuccess: (data) { ... },
onPaymentFailed: (data) { ... },
onClose: () { ... },
);
// Use with show()
CinetPay.showFromConfig(context: context, config: config);
// Or with the button
CinetPayButton.fromConfig(config: config, text: 'Pay');
// Or with the full-screen page
CinetPayCheckoutPage.fromConfig(config: config);| Method | Description |
|---|---|
CinetPay.show() |
Opens checkout as a bottom sheet |
CinetPay.showFromConfig() |
Opens checkout from a CheckoutConfig |
CinetPay.openInBrowser() |
Opens checkout in external browser (fallback) |
| Callback | Type | Description |
|---|---|---|
onPaymentSuccess |
PaymentResponse |
Payment accepted |
onPaymentFailed |
PaymentResponse |
Payment refused |
onPaymentPending |
PaymentResponse |
Payment pending (PENDING, INITIATED, EXPIRED) |
onClose |
void |
Checkout closed |
onError |
PaymentError |
Technical error |
| Field | Type | Description |
|---|---|---|
amount |
num |
Amount paid |
currency |
String |
Currency code (XOF, XAF, etc.) |
status |
PaymentStatus |
Payment status enum |
paymentMethod |
String |
Method code (OM, MOMO, WAVE, VISA, etc.) |
description |
String |
Payment description |
transactionId |
String |
CinetPay transaction ID |
metadata |
String? |
Custom metadata |
operatorId |
String? |
Operator transaction ID |
paymentDate |
String? |
Payment date |
| Value | Description |
|---|---|
PaymentStatus.accepted |
Payment confirmed |
PaymentStatus.refused |
Payment refused |
PaymentStatus.pending |
Pending confirmation |
PaymentStatus.initiated |
Initiated, not confirmed |
PaymentStatus.expired |
Payment expired |
PaymentStatus.unknown |
Unknown status |
Helper methods: isSuccess, isFailed, isPending.
| Field | Type | Description |
|---|---|---|
code |
String |
Error code (INVALID_TOKEN, WEBVIEW_ERROR, etc.) |
message |
String |
Human-readable message |
Tokens are validated automatically. You can also validate manually:
if (TokenValidator.isValid(token)) {
// Token format is correct
}
final error = TokenValidator.validate(token);
if (error != null) {
print(error.message);
}sequenceDiagram
participant App as App Flutter
participant B as Votre Backend
participant C as CinetPay API
App->>B: 1. Demande de paiement
B->>C: 2. POST /v1/payment
C-->>B: 3. paymentToken
B-->>App: 4. { paymentToken }
App->>App: 5. CinetPay.show(paymentToken)
App->>C: 6. WebView → checkout
Note over App,C: L'utilisateur paie
C-->>B: 7. Webhook (notifyUrl)
C-->>App: 8. Callback → onPaymentSuccess
- Votre backend initialise le paiement via l'API CinetPay et obtient un
paymentToken - L'app Flutter reçoit ce token et ouvre le checkout dans une WebView (mobile) ou le navigateur (web)
- L'utilisateur paie — le SDK détecte le résultat via postMessage ou interception d'URL
- Le callback approprié est appelé avec un
PaymentResponse
Le paymentToken est obtenu côté serveur. Utilisez le SDK de votre choix :
| SDK | Langage | Installation |
|---|---|---|
cinetpay-js |
Node.js/TypeScript | npm install cinetpay-js |
cinetpay-python |
Python | pip install cinetpay-python |
cinetpay-go |
Go | go get github.com/cinetpay/cinetpay-go |
cinetpay-laravel-sdk |
PHP/Laravel | composer require cinetpay/laravel-sdk |
| API directe | Tout langage | POST /v1/payment |
const { CinetPayClient } = require('cinetpay-js')
const client = new CinetPayClient({
credentials: {
CI: { apiKey: process.env.CINETPAY_API_KEY_CI, apiPassword: process.env.CINETPAY_API_PASSWORD_CI },
},
})
app.post('/api/pay', async (req, res) => {
const payment = await client.payment.initialize({
currency: 'XOF',
merchantTransactionId: `ORDER-${Date.now()}`,
amount: req.body.amount,
lang: 'fr',
designation: 'Achat mobile',
clientEmail: req.body.email,
clientFirstName: req.body.firstName,
clientLastName: req.body.lastName,
successUrl: 'https://monsite.com/success',
failedUrl: 'https://monsite.com/failed',
notifyUrl: 'https://monsite.com/webhook',
channel: 'PUSH',
}, 'CI')
res.json({ paymentToken: payment.paymentToken })
})class PaymentService {
static Future<String> getPaymentToken(int amount) async {
final response = await http.post(
Uri.parse('https://votre-api.com/api/pay'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'amount': amount, 'email': 'client@email.com',
'firstName': 'Jean', 'lastName': 'Dupont'}),
);
final data = jsonDecode(response.body);
return data['paymentToken'];
}
}
// Dans votre widget
ElevatedButton(
onPressed: () async {
final token = await PaymentService.getPaymentToken(5000);
if (mounted) {
CinetPay.show(
context: context,
paymentToken: token,
onPaymentSuccess: (data) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payé ! ${data.amount} ${data.currency}')),
);
},
);
}
},
child: const Text('Payer 5000 XOF'),
)| Pays | Code | Opérateurs |
|---|---|---|
| Côte d'Ivoire | CI | Orange Money, Moov, MTN, Wave |
| Sénégal | SN | Orange Money, Free, Expresso, Wave |
| Cameroun | CM | Orange Money, MTN |
| Burkina Faso | BF | Orange Money, Moov, Wave |
| Mali | ML | Orange Money, Moov |
| Togo | TG | Moov, TMoney |
| Guinée | GN | Orange Money, MTN |
| Bénin | BJ | Moov, MTN |
| RD Congo | CD | Orange Money, Airtel, M-Pesa, Africell |
| Niger | NE | Airtel, Moov, Zamani |
- Les credentials (
apiKey/apiPassword) restent côté serveur — l'app ne reçoit qu'unpaymentTokenopaque - Le
paymentTokenest validé (alphanumérique, 10-128 caractères) avant chargement - La WebView n'autorise que les domaines CinetPay
- Les URLs externes sont ouvertes dans le navigateur système
| Plateforme | Comportement |
|---|---|
| Android | WebView intégrée (minSdkVersion 19) |
| iOS | WebView intégrée (iOS 12+) |
| Web | Ouvre le navigateur + écran d'attente |
- Flutter 3.10+
- Dart 3.0+
| Package | Cible |
|---|---|
cinetpay-js |
Backend Node.js |
cinetpay-python |
Backend Python |
cinetpay-go |
Backend Go |
cinetpay-seamless |
Frontend web |
cinetpay_flutter |
Mobile iOS/Android/Web |
cinetpay-mcp |
AI assistants |
Pour toute question sur l'API CinetPay : support@cinetpay.com
MIT