NaabigaPay · Flutter
Docs complètes Ouvrir l'app →
Flutter · Dart

Intégrer NaabigaPay dans Flutter

Accepter des paiements NaabigaPay dans votre application, en trois étapes. Rien d'autre.

À lire en premier. Votre clé secrète (sk_live_…) ne doit jamais être mise dans l'app Flutter : un APK se décompile et la clé fuiterait. L'app parle à votre serveur ; c'est votre serveur qui appelle NaabigaPay avec la clé.

Le principe

Flux
App Flutter  ─►  VOTRE serveur (détient sk_live_…)  ─►  API NaabigaPay /v1
     ▲                      ▲
     │ ouvre l'URL          │ reçoit le webhook signé « payment.succeeded »
     │ de paiement          │ et marque la commande comme payée
  1. L'app demande un lien de paiement à votre serveur.
  2. L'app ouvre l'URL renvoyée (page de paiement NaabigaPay).
  3. Le paiement confirmé, NaabigaPay appelle le webhook de votre serveur ; l'app relit le statut chez vous.

1.Créer le lien (côté serveur)

Votre serveur appelle POST/v1/payment-links avec la clé secrète. Exemple en Dart (le même principe en Node, PHP, Python…) :

Dart · serveur
import 'dart:convert';
import 'package:http/http.dart' as http;

class NaabigaPay {
  NaabigaPay(this.secret); // sk_live_... — RESTE SUR LE SERVEUR
  final String secret;
  final String base = 'https://api.pay.naabiga.com/api/v1';

  Map<String, String> get _h => {
    'Authorization': 'Bearer $secret',
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };

  /// Crée un lien de paiement (montant en FCFA). Renvoie { token, url, ... }.
  Future<Map<String, dynamic>> createPaymentLink({
    required int amount,
    String? reference,      // votre numéro de commande
    String? description,
    String? returnUrl,      // deep link de retour vers l'app
    String? cancelUrl,
    String? idempotencyKey, // rejeu réseau sans double-création
  }) async {
    final res = await http.post(
      Uri.parse('$base/payment-links'),
      headers: { ..._h, if (idempotencyKey != null) 'Idempotency-Key': idempotencyKey },
      body: jsonEncode({
        'amount': amount,
        if (reference != null) 'reference': reference,
        if (description != null) 'description': description,
        if (returnUrl != null) 'return_url': returnUrl,
        if (cancelUrl != null) 'cancel_url': cancelUrl,
        'single_use': true,
      }),
    );
    if (res.statusCode != 201) {
      throw Exception('NaabigaPay ${res.statusCode}: ${res.body}');
    }
    return jsonDecode(res.body) as Map<String, dynamic>;
  }

  /// Statut d'un paiement : pending | success | failed | cancelled.
  Future<Map<String, dynamic>> getPayment(String reference) async {
    final res = await http.get(Uri.parse('$base/payments/$reference'), headers: _h);
    if (res.statusCode != 200) {
      throw Exception('NaabigaPay ${res.statusCode}: ${res.body}');
    }
    return jsonDecode(res.body) as Map<String, dynamic>;
  }
}

La réponse contient url (ex. https://pay.naabiga.com/link/9f3c…) : c'est la page où le client paie. Pour un montant libre, n'envoyez pas amount mais une description (bornes optionnelles amount_min / amount_max).

2.Ouvrir le paiement dans l'app

L'app demande le lien à votre serveur, puis ouvre l'url avec url_launcher :

Dart · app Flutter
import 'package:url_launcher/url_launcher.dart';

Future<void> payer(String commandeId) async {
  // 1) Votre serveur crée le lien (il a la clé) et renvoie l'URL de paiement.
  final data = await monBackend.creerLien(
    commande: commandeId,                    // -> reference NaabigaPay
    returnUrl: 'monapp://paiement/retour',   // deep link de votre app
    cancelUrl: 'monapp://paiement/annule',
  );

  // 2) Ouvrir la page de paiement NaabigaPay (Mobile money / solde).
  await launchUrl(Uri.parse(data['url']), mode: LaunchMode.externalApplication);
}

Astuce. LaunchMode.externalApplication ouvre le navigateur système (recommandé pour le mobile money). Vous pouvez aussi utiliser une WebView in-app et intercepter la navigation vers votre return_url.

3.Confirmer le paiement

Deux signaux, un seul fait foi :

Capter le retour avec app_links puis vérifier auprès de votre serveur :

Dart · retour + vérification
import 'package:app_links/app_links.dart';

final _appLinks = AppLinks();

void ecouterRetourPaiement() {
  _appLinks.uriLinkStream.listen((uri) async {
    if (uri.scheme == 'monapp' && uri.host == 'paiement') {
      // Demander le statut à VOTRE serveur (qui a reçu le webhook signé).
      final statut = await monBackend.statutCommande(/* commandeId */);
      // statut == 'success' -> afficher « Paiement réussi »
    }
  });
}

Webhook (rappel côté serveur)

NaabigaPay envoie POST à l'URL enregistrée dans le portail marchand, avec :

En-têteValeur
X-NPay-Eventpayment.succeeded
X-NPay-SignatureHMAC-SHA256 du corps brut, signé avec le secret du webhook

Recalculez le HMAC-SHA256 sur le corps brut avec le secret du webhook, comparez à X-NPay-Signature, et n'acceptez le paiement que si la signature correspond.

Configurer le deep link monapp://

Android — dans android/app/src/main/AndroidManifest.xml, à l'intérieur de l'activité :

AndroidManifest.xml
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="monapp" android:host="paiement" />
</intent-filter>

iOS — dans ios/Runner/Info.plist :

Info.plist
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array><string>monapp</string></array>
  </dict>
</array>

Sandbox et passage en production

Référence rapide

MéthodeEndpointRôle
POST/v1/payment-linksCréer un lien de paiement
GET/v1/payment-links/{token}Détail d'un lien
GET/v1/payments/{reference}Statut d'un paiement
GET/v1/transactionsPaiements reçus (paginé)

Base : https://api.pay.naabiga.com/api/v1 · Auth : Authorization: Bearer sk_…. La documentation complète détaille chaque endpoint et le format des webhooks.