Aller au contenu

Flutter

Le package wajub_mobile et son interface de paiement prête à l'emploi pour les applications Flutter.

Le SDK Flutter vous permet d'encaisser des paiements Mobile Money, par carte et par portefeuille dans votre application, avec une interface de paiement prête à l'emploi.

wajub_mobile

Stable · GA

pub.dev

Version
1.1.1
Runtime
Flutter 3.24+ (Dart 3.5+)
Frameworks
Android and iOS

Couvre

  • Payments
  • Mobile Money
  • Cards

Installer

Ajouter la dépendance
flutter pub add wajub_mobile

Ou fixez-la dans pubspec.yaml avec wajub_mobile: ^1.6.0.

PrérequisVersion
Flutter3.24 ou version ultérieure (environment: flutter: '>=3.24.0')
Dart^3.5.0
flutter_stripe^11.4.0, une dépendance du package : ses propres prérequis deviennent les vôtres
Build AndroidJava 17, compileSdk 35 ou ultérieur, minSdk 24
iOSCible de déploiement 13.0

Les autres dépendances du package ne demandent aucun code de votre part.

pubspec.yaml, sur Flutter 3.24 à 3.26 uniquement
dependency_overrides:
  flutter_stripe: 11.4.0
  stripe_android: 11.4.0
  stripe_ios: 11.4.0
  stripe_platform_interface: 11.4.0
android/app/src/main/kotlin/.../MainActivity.kt
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()

Démarrage rapide

Trois étapes, et seule celle du milieu s'exécute dans l'application.

1. Votre serveur crée le paiement. POST /payments avec votre clé secrète, puis transmettez à l'application l'authorization_token de la réponse. Le package ne détient jamais de clé secrète. Créer un paiement

Votre endpoint et votre client
final response = await http.post(
  Uri.parse('https://api.yourshop.com/checkout'),
  headers: {'Content-Type': 'application/json'},
  body: jsonEncode({'cart_id': cart.id}),
);

final authorizationToken = jsonDecode(response.body)['authorization_token'] as String;

2. L'application présente l'interface de paiement avec ce jeton et lit le résultat.

Toute l'intégration
import 'package:wajub_mobile/wajub_mobile.dart';

Future<void> pay(BuildContext context, String authorizationToken) async {
  final result = await presentWajubCheckout(
    context,
    PresentOptions(sessionToken: authorizationToken),
  );

  switch (result) {
    case CheckoutComplete(:final payment):
      // Paid: payment.id is Wajub's payment id. Confirm it on your server first.
      showReceipt(payment.id);
    case CheckoutProcessing():
      // Closed while a payment was running: money may still move.
      showPending();
    case CheckoutFailed(:final error):
      // A failure, an ended or expired payment, or a load error.
      showError(error.message);
    case CheckoutCancelled(:final reason):
      // "Cancel payment", or closed before any payment.
      debugPrint('cancelled: ${reason.wireName}');
  }
}

3. Votre serveur confirme. Le résultat est ce que le payeur a vu, pas une preuve de paiement. Livrez sur le webhook payment.succeeded, ou après GET /payments/{id} avec payment.id.

presentWajubCheckout se résout exactement une fois, à la fermeture de l'interface, et Wajub.present(context, options) est le même appel. CheckoutResult est une classe scellée : le switch ci-dessus est donc exhaustif. Présenter une seconde interface pendant qu'une autre est ouverte lève une WajubError dont le code est sheet_already_presented, sans toucher à l'interface ouverte. Une nouvelle interface peut s'ouvrir dès que la précédente s'est fermée, y compris depuis son onComplete.

Options

PresentOptions, tous les champs
PresentOptions(
  sessionToken: authorizationToken,
  locale: 'fr',
  appearance: const Appearance(theme: 'night'),
  loader: WajubLoader.skeleton,
  readyTimeout: const Duration(seconds: 15),
  returnUrl: 'https://shop.example.com/app/return',
  mobileMoney: const MobileMoneyOptions(
    country: 'CI',
    countryLocked: true,
    operatorMode: OperatorMode.detect,
  ),
  cancelOnDismiss: true,
  autoCloseSuccess: const Duration(seconds: 3),
  stripe: const StripeOptions(appPublishableKey: 'pk_live_…'), // your app's own Stripe key, if it has one
  callbacks: CheckoutCallbacks(onStateChange: (event) => debugPrint(event.state.wireName)),
)
ChampType, défautRôle
sessionTokenString, obligatoireL'authorization_token du paiement
localeString?'fr' ou 'en'. Sans lui, la langue du paiement, sinon celle de l'appareil. Tout ce qui n'est pas du français donne l'anglais
appearanceAppearance?L'apparence de l'interface, voir Apparence
loaderWajubLoader, skeletonWajubLoader.skeleton, des blocs de substitution aux couleurs de l'interface, ou WajubLoader.spinner
readyTimeoutDuration, 15 sAu-delà, une interface encore en chargement affiche « Impossible de charger le paiement » avec Réessayer, et transmet ready_timeout à onLoadError. Duration.zero attend indéfiniment
returnUrlString?Un lien universel ou App Link https qui revient dans votre application, pour les pages qui s'ouvrent dans le navigateur. Sans lui, le payeur revient de lui-même dans l'application
mobileMoneyMobileMoneyOptionsVoir Mobile Money
cancelOnDismissbool, trueFermer l'interface avant tout paiement annule le paiement. false le laisse ouvert pour que vous puissiez le présenter à nouveau
autoCloseSuccessDuration, 3 sLa durée d'affichage de l'écran de succès. Duration.zero le garde jusqu'à ce que le payeur le ferme
stripeStripeOptionsappPublishableKey, la clé Stripe propre à votre application, voir Cartes
callbacksCheckoutCallbacksVoir ci-dessous

Callbacks et états

Chaque callback est facultatif et s'exécute sur le thread de l'interface, dans cet ordre pour un paiement : onReady, puis onStateChange à chaque changement, onPaymentStarted et onRedirect, puis onSuccess, onError ou onCancel, et onComplete exactement une fois. Un callback qui lève une exception ne casse pas l'interface.

CheckoutCallbacks
CheckoutCallbacks(
  onReady: (ReadyEvent e) {},              // sessionId, environment, amount, currency, methods
  onStateChange: (StateChangeEvent e) {},  // state (CheckoutState), method
  onLoadError: (WajubError error) {},      // the sheet could not offer a payment
  onPaymentStarted: (PaymentStartedEvent e) {}, // method, channel
  onRedirect: (RedirectEvent e) {},        // e.url, right before the system browser opens
  onSuccess: (PaymentSuccess payment) {},
  onError: (WajubError error) {},          // every failure the payer sees, retryable ones included
  onCancel: (CancelEvent e) {},            // reason (CancelReason), redirectUrl
  onComplete: (CheckoutResult result) {},  // what presentWajubCheckout resolves with
)
CallbackDéclenchement
onReadyUne fois, quand le premier écran est dessiné, y compris pour un paiement déjà payé ou terminé. environment vaut sandbox ou live, methods liste les slugs des canaux affichés
onStateChangeAprès onReady, puis à chaque changement. method vaut mobile_money, card, wallet, ou unknown avant tout choix
onLoadErrorLe paiement ne peut pas être proposé : session_not_found, session_terminal_expired, session_terminal_cancelled, session_terminal_failed, load_error, ready_timeout, no_payment_method
onPaymentStartedLa demande de paiement est acceptée. Dès lors, fermer l'interface donne processing
onRedirectJuste avant qu'une page de prestataire s'ouvre dans le navigateur
onSuccessPaiement réussi. method vaut unknown quand le paiement était déjà payé à l'ouverture
onErrorChaque échec que voit le payeur, sauf la validation des champs
onCancelL'interface se ferme comme cancelled
onCompleteL'interface se ferme

state.wireName donne le nom de l'état : INITIATED, COLLECTING_DETAILS, PROCESSING, APPROVAL_REQUIRED, USSD_REQUIRED, PAYER_OTP_REQUIRED, OTP_REQUIRED, PIN_REQUIRED, PHONE_REQUIRED, BIRTHDAY_REQUIRED, ADDRESS_REQUIRED, 3DS_REQUIRED, VERIFYING, PENDING, SUCCESS, FAILED, EXPIRED, CANCELLED. Les cas Dart sont en camel case : CheckoutState.collectingDetails, CheckoutState.payerOtpRequired, CheckoutState.threeDsRequired. Ce que voit le payeur dans chacun figure sur SDKs mobiles.

Résultats

CheckoutResultQuandContenu
CheckoutCompleteL'interface s'est fermée après le succès, y compris pour un paiement déjà payé à l'ouverturepayment (PaymentSuccess), transaction
CheckoutProcessingL'interface s'est fermée pendant un paiement en cours : l'argent peut encore circuler, confirmez sur votre serveurtransaction, éventuellement null
CheckoutFailedFermée sur un échec, un paiement terminé ou expiré, une erreur de chargement, ou un paiement annulé ailleurs (payment_cancelled)error (WajubError), transaction
CheckoutCancelled« Annuler le paiement », une fermeture avant tout paiement, ou la disparition préalable de la page qui l'a présentéereason (CancelReason), redirectUrl

Chaque résultat porte aussi status : complete, processing, failed ou cancelled. CancelReason vaut payerCancelled, dismissed ou hostGone, et reason.wireName donne payer_cancelled, dismissed ou host_gone.

PaymentSuccess porte id (l'identifiant Wajub du paiement, celui que prend GET /payments/{id}), reference (la vôtre), amount (ce que le payeur a payé, frais compris, en unité principale), currency, method, status (toujours success), timestamp (ISO 8601) et receiptUrl.

L'interface n'affiche jamais d'échec quand l'issue est inconnue : elle affiche « Paiement en cours » et continue d'attendre, et la fermer à ce moment donne processing. Quand la page qui a présenté l'interface disparaît, l'interface donne cancelled avec hostGone avant tout paiement, processing une fois un paiement commencé, complete après un succès, et n'annule jamais le paiement. Si le processus de l'application meurt, lisez l'issue sur votre serveur, ou avec WajubSession.status(), au lancement suivant.

Annuler et fermer

Le payeur ferme l'interface avec son bouton de fermeture, un glissement vers le bas, le bouton retour ou un appui en dehors d'elle.

Fermeture…Résultat
Pendant le chargement du paiementCheckoutCancelled, dismissed
Avant tout paiementCheckoutCancelled, dismissed. Le paiement est annulé, sauf si cancelOnDismiss vaut false
Pendant que le payeur approuve, compose un code, confirme une étape ou attendCheckoutProcessing : le paiement continue
Sur l'écran d'échecCheckoutFailed, avec l'erreur que le payeur a vue. Le paiement est annulé s'il peut encore l'être, sauf si cancelOnDismiss vaut false. onCancel ne se déclenche pas
Après le succèsCheckoutComplete

« Annuler le paiement » dans l'interface annule le paiement définitivement et donne CheckoutCancelled avec payerCancelled. Il n'est pas proposé pendant un paiement en cours. Voir SDKs mobiles.

Apparence

L'option appearance règle l'apparence de l'interface. Les clés, les préréglages et les variables figurent sur Apparence.

Appearance
const appearance = Appearance(
  theme: 'stripe',            // presets: stripe, night, flat, none; or light, dark, auto
  colorScheme: 'auto',        // light, dark or auto, which follows the phone
  inherit: true,              // read Theme.of(context) where you set nothing
  variables: AppearanceVariables(
    colorPrimary: '#0F766E',
    colorBackground: '#FFFFFF',
    colorText: '#171717',
    colorDanger: '#B91C1C',
    fontFamily: 'Inter',      // used when your app ships it
    borderRadius: '12px',
    spacingUnit: '4px',
  ),
  buttonTextColor: '#FFFFFF',
  disableAnimations: false,
);

// Or from a map, such as decoded JSON:
final shared = Appearance.fromMap(jsonDecode(appearanceJson) as Map<String, Object?>);

Ce que vous laissez vide vient de l'identité visuelle du marchand définie dans le Dashboard, puis de la palette claire ou sombre par défaut de Wajub. Voir SDKs mobiles.

Mobile Money

Un seul formulaire couvre tous les opérateurs Mobile Money du paiement : le pays, les opérateurs, et le numéro avec l'indicatif du pays, mis en forme pendant la saisie. Le numéro présélectionne son opérateur tant que le payeur n'en a pas choisi un lui-même. Après Payer, l'interface affiche « Validez le paiement sur votre téléphone », ou un code USSD à composer.

MobileMoneyOptionsDéfautRôle
countrynullLe pays de départ, ISO-2. Sans lui, le pays du payeur quand un canal Mobile Money le couvre
countryLockedfalseMasque le sélecteur de pays
operatorModeOperatorMode.chooseOperatorMode.choose liste les opérateurs. OperatorMode.detect affiche l'opérateur du numéro dans le champ du numéro, et la liste seulement quand le numéro ne suffit pas ou que le payeur l'ouvre

Voir SDKs mobiles.

Cartes

Le formulaire de carte de Stripe nécessite FlutterFragmentActivity sur Android, voir Installer. Si votre application encaisse aussi ses propres paiements Stripe avec flutter_stripe, passez la clé de votre application dans stripe: StripeOptions(appPublishableKey: …) pour que vos propres paiements Stripe continuent de fonctionner.

Les autres cartes ouvrent une page sécurisée du prestataire de paiement dans le navigateur du système, et en sandbox l'interface affiche un formulaire de carte de test. Voir SDKs mobiles.

Étapes de confirmation

Quand un paiement demande une vérification de plus, l'interface l'affiche sur son propre écran : un code à usage unique, le code PIN de la carte, un numéro de téléphone, une date de naissance ou une adresse de facturation. Une mauvaise réponse reste sur l'écran avec les essais restants ; une étape expirée ou trop d'essais mènent à l'écran d'échec, avec « Réessayer ». Voir SDKs mobiles.

Erreurs

Chaque échec est une WajubError, une Exception. L'interface la transmet à onLoadError ou onError et la place dans CheckoutFailed.

ChampUtilité
typeUn WajubErrorType : apiError, authenticationError, invalidRequestError, paymentError, rateLimitError
codeLe code sur lequel brancher votre logique
messagePour le payeur, dans la langue de l'interface
declineCodeSur un refus, le code de son motif
retryableSi une nouvelle tentative peut aboutir
paramLe champ en cause, quand il y en a un
correlationIdL'identifiant de requête à communiquer au support de Wajub
retryAfterSecondsSur rate_limited, le temps d'attente
blockReferenceSur shield_blocked, la référence à communiquer au support de Wajub

Les codes utiles à traiter : insufficient_funds, payment_declined, payment_attempt_ended (refusé, rien n'a été débité), payment_failed, payment_cancelled, session_expired, shield_blocked, merchant_restricted, rate_limited, session_terminal_expired, unsupported_action, redirect_unavailable, et sheet_already_presented pour presentWajubCheckout. La liste complète figure sur SDKs mobiles.

Votre propre interface

L'interface de paiement est l'intégration la plus simple. Une application qui dessine ses propres écrans de paiement utilise WajubSession, le client de plus bas niveau, créée avec Wajub.createSession(authorizationToken) et décrite dans la référence API du package sur pub.dev.

Confidentialité

Pour effectuer le paiement, le SDK envoie à Wajub ce que saisit le payeur. Pour la prévention de la fraude, il envoie aussi des informations de base sur l'appareil et un identifiant d'installation aléatoire, qui n'est pas l'identifiant publicitaire. Ce qu'il faut déclarer dans les étiquettes de confidentialité de l'App Store et dans le formulaire de sécurité des données de Google Play, catégorie par catégorie, est sur SDKs mobiles.

Tester

Créez le paiement avec une clé de sandbox : tout le parcours se déroule en sandbox, interface comprise, et l'interface affiche un badge « Mode test ». Les numéros et les cartes de test figurent sur Scénarios de test.

Que pensez-vous de ce contenu ?