bceao_pispi_app 0.0.5 copy "bceao_pispi_app: ^0.0.5" to clipboard
bceao_pispi_app: ^0.0.5 copied to clipboard

SDK Flutter permettant d'intégrer PI-SPI (transactions, demande d'annulation, retour de fond, demande de paiement, QR Code, gestion des alias, gestion des abonnements, notifications, profil, compte) d [...]

bceao_pispi_app #

SDK Flutter permettant d'intégrer PI-SPI (transactions, demande d'annulation, retour de fond, demande de paiement, QR Code, gestion des alias, gestion des abonnements, notifications, profil, compte) dans une application mobile existante, sans reprendre dans son ensemble l'app de référence dont le code source a été partagé avec tous les participants.

Aperçu #

Démo du SDK PI-SPI dans l'app d'exemple

Documentation de l'API #

Le SDK n'expose que le sous-ensemble de l'API PI-SPI dont il a réellement besoin (voir « Ce que le SDK ne fait pas » ci-dessous pour ce qui est volontairement hors périmètre). La référence complète — tous les endpoints, schémas de requête/réponse, exemples — est disponible ici :

developers.pi-bceao.com/api-reference/mob-sdk

Ce que le SDK ne fait pas #

Contrairement à l'app de référence du code source partagé, ce SDK n'implémente aucune authentification :

  • Pas de login, pas de Keycloak/PKCE
  • Pas de vérification PIN/biométrique avant confirmation d'une transaction
  • Pas de refresh de token (le SDK ne parle jamais à un IDP)
  • Pas de fournisseur de notifications push (Firebase, APNs, ...) — voir la section Notifications push pour le point d'intégration fourni

Tout cela reste entièrement à la charge de l'application hôte. Le SDK fait confiance à l'appelant : si l'hôte affiche l'écran du SDK, c'est que l'utilisateur est authentifié et autorisé.

Le SDK ne stocke et ne vérifie aucun secret (ni PIN, ni empreinte) — mais il garantit que la vérification a bien lieu : avant chaque action sensible (confirmer une transaction, supprimer un alias, fermer le compte), il appelle PiSpiIdentificationConfig.requireStepUpIdentification, qui délègue la vérification à l'hôte et ne renvoie qu'un booléen. Voir la section requireStepUpIdentification plus bas.

Intégration #

PiSpiSdkConfig regroupe ses champs par thème : api (backend/authentification), appConfig (identité/ préférences de l'app hôte), identification (step-up) et log (journalisation, optionnel). user et onClose restent au niveau racine.

import 'package:bceao_pispi_app/bceao_pispi_app.dart';

final config = PiSpiSdkConfig(
  user: PiSpiUserIdentity(
    firstName: user.firstName,
    lastName: user.lastName,
    fullName: user.fullName,
    // Voir PiSpiUserIdentity.supportedPaysCodes pour les contraintes
    // (categorie: P/C, genre: 1/2, pays: code UEMOA).
    categorie: user.categorie,
    nationalite: user.nationalite,
    paysResidence: user.paysResidence,
    genre: user.genre,
    pays: user.pays,
    avatar: user.avatarUrl,
    // La liste des comptes du client — jamais vide, voir la section
    // "Comptes multiples" plus bas. Un seul élément si le client n'a
    // qu'un compte.
    comptes: [PiSpiCompte(numero: user.accountNumber, libelle: 'Courant', solde: user.solde)],
  ),
  api: PiSpiApiConfig(
    apiBaseUrl: 'https://api.exemple-participant.com',
    // Token valide au moment de l'ouverture du SDK.
    accessToken: monAuthService.currentAccessToken,
    // Appelé quand une requête échoue en 401/403 avec le token courant :
    // l'hôte le rafraîchit et retourne le nouveau token (ou null s'il n'y
    // parvient pas). Après 3 échecs, le SDK appelle onSessionExpired.
    onAccessTokenExpired: () async => monAuthService.refreshAccessToken(),
    // Appelé si onAccessTokenExpired ne parvient plus à fournir de token
    // valide après 3 tentatives : une vraie erreur de session.
    onSessionExpired: () async {
      Navigator.of(context).popUntil((r) => r.isFirst);
      monAuthService.redirectToLogin();
    },
  ),
  appConfig: PiSpiAppConfig(
    pspCodeMembre: 'SNB000',
    // Langue d'affichage du SDK (fr/en/pt) : l'hôte gère son propre
    // paramétrage de langue, le SDK n'en a pas. Voir "Localisation" plus bas.
    defaultLocale: Locale(monAuthService.langueActuelle), // ex. 'fr'
  ),
  identification: PiSpiIdentificationConfig(
    // Appelé avant chaque action sensible : à vous de vérifier PIN/biométrie
    // et de renvoyer true/false. Voir la section dédiée plus bas.
    requireStepUpIdentification: () async => monAuthService.demanderConfirmation(),
  ),
  // Optionnel : appelé pour une fermeture volontaire du SDK (bouton retour
  // sans écran derrière, bouton de fermeture de l'accueil...) — pas une
  // erreur. Si omis, api.onSessionExpired est utilisé à la place. Voir
  // "onClose vs onSessionExpired" plus bas.
  onClose: () async {
    Navigator.of(context).popUntil((r) => r.isFirst);
  },
);

Navigator.of(context).push(MaterialPageRoute(
  builder: (_) => PiSpiSdkEntry(config: config),
));

PiSpiUserIdentity #

Identité de l'utilisateur connecté côté hôte. Le SDK ne gère aucune authentification lui-même : ces informations sont uniquement affichées (nom, avatar) ou utilisées pour construire les requêtes (numéro de compte). Aucune n'est vérifiée ni stockée par le SDK au-delà de la session en cours.

Champ Type Requis Description
firstName String oui Prénom du client.
lastName String oui Nom du client.
fullName String oui Nom complet affiché dans l'UI du SDK (voir nomComplet(), qui s'y replie sur firstName+lastName si vide).
categorie String oui "P" (personne physique) ou "C" (commerçant). Toute autre valeur déclenche une assertion.
nationalite String oui Code pays de nationalité du client.
paysResidence String oui Code pays de résidence du client.
genre String oui "1" (masculin) ou "2" (féminin). Toute autre valeur déclenche une assertion.
pays String oui Code pays UEMOA de domiciliation du compte (pas nécessairement celui du client) : BJ, BF, CI, GW, ML, NE, SN ou TG (voir PiSpiUserIdentity.supportedPaysCodes). Toute autre valeur déclenche une assertion.
comptes List<PiSpiCompte> oui Comptes du client, jamais null ni vide — voir « Comptes multiples » plus bas.
avatar String? non URL de la photo de profil du client, affichée dans l'UI du SDK.
currentAccount String? non Compte actif par défaut avant toute sélection — voir « Comptes multiples » plus bas.

PiSpiCompte (élément de comptes) :

Champ Type Description
numero String Numéro du compte (ex. numero de compte bancaire, numero de téléphone EME ou tout autre identifiant de compte du client).
libelle String Libellé affiché à l'utilisateur (ex. "Courant", "Épargne").
solde double Solde du compte, affiché dans l'écran de sélection.

Comptes multiples (PiSpiUserIdentity.comptes) #

comptes ne doit jamais être null ni vide : c'est la liste des comptes bancaires du client.

PiSpiUserIdentity(
  // ...
  comptes: [
    PiSpiCompte(numero: 'SN0011...431', libelle: 'Courant', solde: 9240320),
    PiSpiCompte(numero: 'SN0011...432', libelle: 'Chèque', solde: 240000),
    PiSpiCompte(numero: 'SN0011...430', libelle: 'Épargne', solde: 32240320),
  ],
)

Un seul compte : rien à faire de plus, il est utilisé directement — pas d'écran de sélection, pas de carrousel.

Plusieurs comptes : à chaque ouverture du SDK, un écran "Sélectionner un compte" s'affiche (juste après l'introduction/permissions au premier lancement, sinon directement) — le client choisit celui à utiliser pour cette session. Ce choix devient ensuite le compte actif de toute la session (alias PI-SPI recherché/créé pour lui, QR partagé, transactions envoyées...) jusqu'à la fermeture du SDK. Le compte actif peut aussi être changé sans rouvrir le SDK, via un carrousel horizontal sur la carte compte de l'accueil : swiper d'un compte à l'autre re-sélectionne immédiatement le compte actif ; si le compte affiché n'a pas encore d'alias PI-SPI, l'écran de création s'ouvre automatiquement pour lui (directement sur le choix du type d'alias — le compte est déjà connu, il n'est jamais redemandé).

currentAccount reste disponible, optionnel : c'est la valeur par défaut avant que le client n'ait choisi un compte (non fourni, comptes.first.numero est utilisé) — l'écran de sélection s'affiche tout de même à chaque ouverture dès que comptes en contient plusieurs, quelle que soit la valeur de currentAccount.

Localisation #

Contrainte : le SDK ne peut être utilisé qu'en français, anglais ou portugais (fr, en, pt) — ce sont les seules langues gérées par AppLocalizations. Passer une autre langue à defaultLocale déclenche une assertion en debug (PiSpiAppConfig.supportedLocaleCodes), et se replie sur la locale du delegate le plus proche en release.

Le SDK possède son propre AppLocalizations (fr/en/pt). Pour que les textes s'affichent, ajoutez son delegate à votre propre MaterialApp :

MaterialApp(
  localizationsDelegates: [
    ...GlobalMaterialLocalizations.delegates,
    AppLocalizations.delegate, // celui exporté par ce package
  ],
  supportedLocales: AppLocalizations.supportedLocales,
  // ...
)

Le SDK n'a pas son propre sélecteur de langue dans ses écrans de paramètres : c'est PiSpiAppConfig.defaultLocale qui détermine la langue affichée (fr par défaut), indépendamment de la locale ambiante de l'application hôte. C'est à l'hôte de gérer son propre paramétrage de langue et de transmettre la valeur actuelle :

PiSpiSdkConfig(
  // ...
  appConfig: PiSpiAppConfig(
    // ...
    defaultLocale: Locale(monAuthService.langueActuelle), // 'fr', 'en' ou 'pt' uniquement
  ),
)

Si votre app hôte supporte d'autres langues, mappez-les vers la langue la plus proche parmi fr/en/pt avant de les transmettre au SDK.

defaultTheme #

Champ optionnel (PiSpiAppConfig.defaultTheme) : si vous le fournissez, il est appliqué immédiatement et persisté comme nouveau thème par défaut à chaque ouverture du SDK — y compris si le client avait lui-même changé de thème depuis les écrans de paramètres du SDK lors d'une session précédente. Si vous ne le fournissez pas (null), la préférence locale actuelle est conservée telle quelle (ou Themes.light si aucune n'existe encore).

PiSpiSdkConfig(
  // ...
  appConfig: PiSpiAppConfig(
    // ...
    defaultTheme: Themes.dark, // optionnel ; omettre pour ne rien imposer
  ),
)

Une fois le SDK ouvert, l'utilisateur reste libre de changer de thème depuis ses écrans de paramètres — si vous l'y autorisez, voir canChangeTheme ci-dessous ; ce choix persiste jusqu'à la prochaine ouverture où l'hôte fournit lui-même une valeur.

canChangeTheme #

Champ optionnel (PiSpiAppConfig.canChangeTheme) : autorise le client à changer lui-même de thème depuis les écrans de paramètres du SDK. Tant qu'il vaut false, l'entrée « Thème » est masquée dans les paramètres, et le thème reste celui fixé par defaultTheme (ou la préférence déjà persistée) pour toute la session.

Non fourni, sa valeur par défaut dépend de defaultTheme : false si vous fournissez un defaultTheme (vous l'imposez alors pour la session, cohérent avec defaultLocale), true sinon (vous n'avez exprimé aucun avis sur le thème, donc autant laisser le client choisir). Passez canChangeTheme explicitement pour découpler ce comportement de defaultTheme.

PiSpiSdkConfig(
  // ...
  appConfig: PiSpiAppConfig(
    // ...
    defaultTheme: Themes.dark,
    canChangeTheme: true, // sinon, comme defaultTheme est fourni, l'entrée « Thème » resterait masquée
  ),
)

La page d'accueil du SDK affiche toujours la variante avec QR code — il n'y a pas de choix de mise en page à configurer.

Thèmes disponibles #

Le SDK propose 7 thèmes (Themes.system, .light, .dark, .yellow, .green, .blue, .red) — voir defaultTheme et canChangeTheme ci-dessus pour les imposer ou laisser le client choisir depuis les écrans de paramètres du SDK.

Aperçu Valeur Description
Themes.system Suit le thème clair/sombre de l'appareil. Défaut si aucun thème n'est fourni ni déjà persisté.
Themes.light Thème clair.
Themes.dark Thème sombre.
Themes.yellow Thème clair, accent jaune.
Themes.green Thème clair, accent vert.
Themes.blue Thème clair, accent bleu.
Themes.red Thème clair, accent rouge.

Permissions natives (Android / iOS) #

Ce SDK est un package Dart pur : il ne peut pas déclarer ses propres permissions natives, contrairement à un plugin. C'est à chaque application hôte de les ajouter elle-même, sinon les fonctionnalités correspondantes (scan QR, contacts, géolocalisation...) échoueront silencieusement ou seront refusées par l'OS sans qu'aucune boîte de dialogue de permission n'apparaisse.

Android — dans android/app/src/main/AndroidManifest.xml :

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />

iOS — dans ios/Runner/Info.plist :

<key>NSCameraUsageDescription</key>
<string>...pour scanner les QR codes</string>
<key>NSContactsUsageDescription</key>
<string>...pour envoyer de l'argent à un contact</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>...pour vérifier vos transactions et prévenir la fraude</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>...pour importer un QR code depuis la galerie</string>

Voir example/android/app/src/main/AndroidManifest.xml et example/ios/Runner/Info.plist pour une version prête à copier.

requireStepUpIdentification #

Le SDK appelle ce callback juste avant de dispatcher une action sensible (confirmer une transaction, un envoi programmé, une demande de paiement acceptée, un retour de fonds, une annulation, supprimer un alias, fermer le compte). Si le callback renvoie false, l'action est simplement annulée.

requireStepUpIdentification: () async {
  // Remplacez par votre propre vérification (écran PIN, local_auth...).
  final ok = await monAuthService.demanderPinOuBiometrie();
  return ok;
},

Le SDK ne connaît jamais le PIN ni la méthode utilisée : c'est volontaire, pour ne pas dupliquer un mécanisme de sécurité que l'app hôte possède déjà (voir PiSpiIdentificationConfig.confirmationMethodLabel pour indiquer a posteriori la méthode employée). Ce callback est ce qui garantit qu'aucune action sensible ne peut être confirmée sans step-up, même si un développeur oublie de le faire lui-même avant d'appeler le SDK.

Notifications push #

Le SDK n'embarque aucun fournisseur de push, ni la logique d'enregistrement du token auprès du backend : c'est l'application hôte qui gère sa propre configuration de push de bout en bout (Firebase, APNs, OneSignal...) — y compris l'enregistrement du token auprès du backend PI-SPI. PiSpiPush ne fait qu'un pont entre un message push déjà reçu par l'hôte et l'UI du SDK :

// Dans le handler de message push de l'hôte (ex. FirebaseMessaging.onMessage) :
FirebaseMessaging.onMessage.listen((message) {
  PiSpiPush.handleIncoming(
    message.data, // notificationId, idObject, type, montant, clientAlias, ...
    title: message.notification?.title,
    body: message.notification?.body,
  );
});

handleIncoming republie l'événement dans le bus interne du SDK, ce qui déclenche le même comportement (toast en app, rafraîchissement de la liste, badge) que dans l'app de référence — sans que le SDK ait besoin de savoir comment le push a été reçu. Le contenu exact de message.data dépend du type de notification à l'origine du push : voir le schéma NotificationPushData de l'openapi du SDK.

Thème #

Le SDK n'impose pas son propre MaterialApp : il hérite du Theme et de la Localizations ambiants de l'hôte, et surcharge uniquement le Theme localement pour respecter la préférence clair/sombre choisie dans les paramètres du SDK.

Première utilisation (introduction + permission contacts) #

Comme dans l'app de référence, la toute première fois que l'utilisateur ouvre le SDK (tant qu'il n'a jamais atteint la fin de l'introduction), PiSpiSdkEntry affiche automatiquement, avant l'accueil :

  1. L'introduction (6 écrans vidéo swipeables) — le dernier écran a un bouton "Continuer" (au lieu du bouton de connexion de l'app de référence, puisque le SDK n'a pas de login) qui marque ConfigKey.introductionPassed et enchaîne directement sur l'étape suivante.
  2. La demande de permission contacts — explique pourquoi le SDK demande l'accès aux contacts avant de la solliciter réellement. Contrairement à l'app de référence, cette étape ne redemande pas la permission notification (le SDK n'a pas de fournisseur de push, voir Notifications push).

Cet état (ConfigKey.introductionPassed) est persisté localement par le SDK lui-même : il n'y a rien à configurer côté hôte, et ce flow ne s'affiche plus jamais une fois passé, même si l'utilisateur revient sur l'app.

confirmationMethodLabel #

L'API persiste la méthode de confirmation utilisée pour une transaction ("pin", "fingerprint", ... dans l'app de référence). Le SDK n'ayant pas connaissance de la politique de sécurité de l'hôte, il envoie la valeur de PiSpiIdentificationConfig.confirmationMethodLabel (par défaut "external"). Vérifiez avec l'équipe backend PI-SPI si ce champ a une contrainte d'énumération avant mise en production.

logLevel #

Niveau de journalisation du SDK (package:logger), configurable via PiSpiLogConfig.logLevel : "debug", "trace", "info" (défaut), "warning", "error", "fatal" ou "off". Une valeur inconnue équivaut à tout journaliser.

PiSpiSdkConfig(
  // ...
  log: PiSpiLogConfig(
    logLevel: 'warning', // à réduire en production
  ),
)

Le SDK n'installe pas de crash reporting lui-même (voir "Ce que le SDK ne fait pas") : ces logs sont uniquement destinés au débogage pendant l'intégration.

onFlutterError #

Optionnel : callback appelé pour chaque erreur Flutter interceptée pendant que le SDK est affiché, pour que l'hôte la remonte à son propre outil de crash reporting (Crashlytics, Sentry...).

PiSpiSdkConfig(
  // ...
  log: PiSpiLogConfig(
    onFlutterError: (details) {
      MyCrashReporter.recordFlutterError(details);
    },
  ),
)

Ce callback ne fait que transmettre l'erreur : il n'écrase jamais un gestionnaire FlutterError.onError déjà défini par l'hôte (celui-ci continue d'être appelé juste après), et le SDK n'affiche aucun écran d'erreur ni ne quitte l'application à sa place — contrairement à l'app de référence, cela reste entièrement la décision de l'hôte.

onClose vs onSessionExpired #

Le SDK rend la main à l'hôte dans deux situations bien différentes :

  • api.onSessionExpired (obligatoire) : une vraie erreur — api.onAccessTokenExpired ne parvient plus à fournir de token valide après 3 tentatives (401/403 persistant). L'hôte voudra probablement rediriger vers sa propre connexion.
  • onClose (optionnel, au niveau racine de PiSpiSdkConfig) : une fermeture volontaire, sans erreur — le client a fermé le SDK depuis un bouton retour ou de fermeture, ou vient de clôturer son compte. Il n'y a simplement plus rien à afficher.

Si onClose n'est pas fourni, le SDK utilise api.onSessionExpired à sa place (comportement historique, quand un seul callback existait pour les deux cas). Ne fournissez onClose que si vous voulez réagir différemment à ces deux situations (typiquement : ne pas afficher un message "session expirée" lors d'une fermeture volontaire).

Développement #

flutter pub get
flutter analyze
flutter test

Le dossier example/ contient une application hôte factice qui pousse PiSpiSdkEntry pour valider l'intégration de bout en bout.

0
likes
120
points
--
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

SDK Flutter permettant d'intégrer PI-SPI (transactions, demande d'annulation, retour de fond, demande de paiement, QR Code, gestion des alias, gestion des abonnements, notifications, profil, compte) dans une application mobile existante.

Repository (GitHub)

License

unknown (license)

Dependencies

audioplayers, calendar_date_picker2, dio, emoji_picker_flutter, encrypt, file_picker, flutter, flutter_bloc, flutter_client_sse, flutter_contacts, flutter_localizations, flutter_secure_storage, flutter_svg, geolocator, go_router, google_mlkit_barcode_scanning, hive_flutter, image_picker, intl, json_annotation, logger, mobile_scanner, path_provider, pdf, permission_handler, printing, qr, share_plus, shared_preferences, url_launcher, video_player, webview_flutter

More

Packages that depend on bceao_pispi_app