Flutter
Le package wajub_mobile, une interface de paiement native et aucune WebView.
wajub_mobile encaisse les paiements dans votre application Flutter. Les champs Mobile Money sont
des widgets Flutter natifs, les champs de carte proviennent de flutter_stripe et le payeur ne voit jamais de WebView.
wajub_mobile
Stable · GApub.dev
- Version
- 1.1.1
- Runtime
- Dart 3.5+, Flutter with Material
- Frameworks
- Android and iOS
Couvre
- Payments
- Mobile Money
- Cards
Installer
flutter pub add wajub_mobileIl installe aussi flutter_stripe, pusher_channels_flutter, url_launcher, http et meta.
La dépendance Stripe est obligatoire. Vous devez donc respecter ses prérequis de plateforme.
| Plateforme | Prérequis de flutter_stripe 11.4 |
|---|---|
| Android | minSdkVersion 21, Kotlin 1.8 ou version ultérieure, Android Gradle Plugin 8 ou version ultérieure |
| Android | MainActivity étend FlutterFragmentActivity avec un descendant de Theme.AppCompat |
| iOS | Cible de déploiement 13.0 ou version ultérieure |
Android nécessite FlutterFragmentActivity
Les champs de carte Stripe s'affichent comme un fragment. Si MainActivity continue d'étendre
FlutterActivity, l'onglet de carte plante dès son ouverture, tandis que l'onglet Mobile Money
continue de fonctionner. Le problème ressemble alors à un bug de carte au lieu d'une erreur de configuration.
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()Obtenir un jeton depuis votre serveur
Aucun élément de ce package n'appelle /payments ni ne détient de clé secrète. Votre backend crée
le paiement et renvoie authorization_token.
final response = await http.post(
Uri.parse('https://api.yourshop.com/checkout'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'cart_id': cart.id}),
);
final token = jsonDecode(response.body)['authorization_token'] as String;Afficher l'interface de paiement
Deux lignes suffisent. L'interface charge la session, liste les opérateurs, collecte le numéro de téléphone ou la carte et traite le paiement.
import 'package:wajub_mobile/wajub_mobile.dart';
Future<void> pay(BuildContext context, String token) async {
final session = Wajub.createSession(token);
await showWajubPaymentSheet(
context: context,
session: session,
onResult: (result) {
switch (result) {
case PaymentComplete(:final transaction):
context.go('/orders/${transaction.reference}');
case PaymentProcessing(:final instruction):
showDialog(context: context, builder: (_) => AwaitingApproval(instruction));
case PaymentRequiresAction():
// The sheet already opened the browser for you.
break;
case PaymentFailed(:final error):
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.message)),
);
}
},
);
}PaymentResult est une classe scellée. Le switch ci-dessus est donc exhaustif et le compilateur
signale tout cas manquant. Utilisez-le plutôt que de lire une chaîne de statut.
| Cas | Résultat |
|---|---|
PaymentComplete | Paiement réglé. transaction contient la référence |
PaymentProcessing | Envoyé à l'opérateur. instruction contient le texte à afficher au payeur |
PaymentRequiresAction | 3DS ou redirection bancaire. L'interface a déjà ouvert le navigateur |
PaymentFailed | error contient code, declineCode et retryable |
Processing est le résultat normal pour Mobile Money
Un paiement Mobile Money renvoie rarement PaymentComplete depuis l'interface. Le payeur doit
encore approuver sur son téléphone. Vous recevez donc PaymentProcessing avec une instruction,
comme composer un code USSD. Affichez ce texte, puis attendez watchStatus ou votre webhook.
Suivre le résultat
final subscription = session.watchStatus().listen((result) {
if (result is PaymentComplete) {
setState(() => _state = OrderState.paid);
}
if (result is PaymentFailed) {
setState(() => _state = OrderState.failed);
}
});
@override
void dispose() {
subscription.cancel();
super.dispose();
}watchStatus s'abonne avec Pusher lorsque la session contient des paramètres de temps réel. Sinon,
il se replie sur un polling toutes les cinq secondes. Vous n'avez rien à choisir, mais devez annuler
l'abonnement dans dispose.
Vos propres écrans à la place de l'interface
WajubSession représente toute l'API sous l'interface de paiement. Utilisez-la si le paiement doit
s'intégrer à un design existant.
final session = Wajub.createSession(token);
final data = await session.loadSession();
// data.transaction.amount, data.transaction.currency, data.branding, data.locale
final operators = data.channels
.where((c) => const {'mobile_money', 'mobile'}.contains(c.type.toLowerCase()))
.toList();
final result = await session.payMobileMoney(
MobileMoneyInput(
channelSlug: operators.first.slug,
phone: '+237670000000',
country: 'CM',
),
);| Méthode | Fonction |
|---|---|
loadSession({forceRefresh}) | Montant, devise, canaux, image de marque et locale. Mis en cache après le premier appel |
getSdkConfig() | Paramètres du prestataire par canal, dont la clé publique Stripe |
payMobileMoney(input) | Envoie le push à l'opérateur |
payCard(...) | Traite une PaymentMethod Stripe déjà créée |
payCardWithStripeElements(...) | Initialise Stripe et crée le moyen de paiement en un appel |
process(channel, data) | Solution brute pour tous les canaux |
handleRedirectAction(result) | Ouvre une URL 3DS ou bancaire dans le navigateur système |
watchStatus({interval}) | Flux de résultats en temps réel ou par polling |
cancel() | Abandonne la session et renvoie l'URL de redirection |
cardChannelSlug() | Canal de carte de la session chargée, s'il existe |
Cartes sans l'interface de paiement
La clé publique provient de la session, pas de votre code, car elle diffère entre la sandbox et le mode live, ainsi qu'entre les marchands.
final session = Wajub.createSession(token);
await session.loadSession();
final config = await session.getSdkConfig();
final slug = session.cardChannelSlug();
final publishableKey = config.channels[slug]?.publishableKey;
if (slug != null && publishableKey != null) {
final result = await session.payCardWithStripeElements(
channelSlug: slug,
publishableKey: publishableKey,
cardholderName: 'Amina Diallo',
);
await session.handleRedirectAction(result); // opens 3DS if needed
}handleRedirectAction ouvre le navigateur système, pas une WebView
Il utilise url_launcher. Le payeur quitte donc votre application pour Chrome ou Safari, puis
revient par votre URL de callback. Ce comportement est volontaire : de plus en plus de banques
refusent les vérifications 3DS dans une WebView. Vérifiez que votre callback sur POST /payments
pointe vers un lien profond géré par votre application.
Erreurs
try {
await session.payMobileMoney(input);
} on WajubError catch (error) {
if (error.type == WajubErrorType.rateLimitError) {
showRetryIn(error.retryAfterSeconds ?? 60);
return;
}
if (error.retryable) {
showRetryButton(error.message);
return;
}
showFatal(error.message, error.correlationId);
}| Champ | Utilité |
|---|---|
type | apiError, authenticationError, invalidRequestError, paymentError ou rateLimitError |
code | Motif lisible par la machine |
declineCode | Présent sur une réponse 402, motif du refus du prestataire |
retryable | Indique si une nouvelle tentative doit être proposée |
param | Champ fautif lors d'une erreur de validation |
correlationId | Valeur à communiquer au support |
retryAfterSeconds | Attente imposée par une limite de requêtes |
Deux noms à connaître
Wajub.initialize(publicKey:) enregistre une clé pk. pour les helpers Stripe. Cette opération est
facultative et l'interface n'en a pas besoin, car la clé publique provient de getSdkConfig().
WajubMobile est un ancien alias
Les anciens exemples appellent WajubMobile.createSession(...). WajubMobile est maintenant un
typedef de Wajub et compile encore, mais le nouveau code doit utiliser Wajub.
Tests
L'origine de l'API est une constante. Vous ne pouvez donc pas rediriger son URL de base. Configurez plutôt une clé de sandbox sur votre backend. Le jeton généré place tout le flux dans la sandbox, interface comprise. Les numéros qui produisent chaque résultat figurent dans Scénarios de test.
Pages associées
- SDKs mobilesL'architecture commune aux trois SDKs.
- Créer un paiementL'appel serveur qui génère le jeton.
- WebhooksLa confirmation qui déclenche la livraison de la commande.
- Moyens de paiement et canauxTous les opérateurs et slugs de canaux.
- React NativeLa même conception en TypeScript.
- AndroidLa même conception en Kotlin et Compose.