Guide

Guide Flutter pour exécuter le flux de transaction YKNPG depuis une app mobile (démo de développement).

Guide Flutter pour exécuter le flux de transaction YKNPG depuis une app mobile (démo de développement).

Note sécurité

  • Stockez le jeton bearer de façon sûre (config distante ou proxy backend). Utiliser .env dans l'app sert uniquement aux tests locaux.

Pré-requis

  • SDK Flutter, Dart 3+.
  1. Créer l'app
  • flutter create demo_yknpg && cd demo_yknpg
  1. Ajouter les dépendances
  • flutter pub add http flutter_dotenv
  1. Ajouter le fichier d'environnement
  • Fichier : .env
YKNPG_API_TOKEN=remplacez-par-votre-jeton
YKNPG_BASE_URL=https://yknpg.ngoul.com/api/v1
  • Mettre à jour pubspec.yaml pour inclure le fichier :
flutter:
  assets:
    - .env
  1. Créer un helper API
  • Fichier : lib/yknpg_api.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;

final _baseUrl = dotenv.env['YKNPG_BASE_URL'] ?? 'https://yknpg.ngoul.com/api/v1';
final _headers = {
  HttpHeaders.authorizationHeader: 'Bearer ${dotenv.env['YKNPG_API_TOKEN']}',
  HttpHeaders.contentTypeHeader: 'application/json',
};

Future<Map<String, dynamic>> startTransaction(int amount, String phone) async {
  final payload = {
    'amount': amount,
    'provider': 'ORANGE_MONEY',
    'user_infos': {'number': phone},
    'callback_url': 'http://example.com',
    'callback_method': 'POST',
    'your_message': 'pay this',
    'your_order_ref': 'demo-1',
    'provider_fees_on_customer': true,
  };
  final create = await http.post(Uri.parse("$_baseUrl/transactions/"),
      headers: _headers, body: jsonEncode(payload));
  if (create.statusCode >= 400) throw create.body;
  final created = jsonDecode(create.body) as Map<String, dynamic>;

  final pay = await http.post(
      Uri.parse("$_baseUrl/transactions/${created['uuid']}/pay/"),
      headers: _headers);
  if (pay.statusCode >= 400) throw pay.body;
  final paid = jsonDecode(pay.body) as Map<String, dynamic>;

  var status = paid['status'] as String;
  while (status == 'new' || status == 'paying') {
    await Future.delayed(const Duration(seconds: 5));
    final poll = await http.get(
        Uri.parse("$_baseUrl/transactions/${created['uuid']}/"),
        headers: _headers);
    if (poll.statusCode >= 400) throw poll.body;
    status = (jsonDecode(poll.body) as Map<String, dynamic>)['status'] as String;
  }

  return {
    'created': created,
    'paid': paid,
    'finalStatus': status,
  };
}
  1. Mettre à jour l'UI
  • Fichier : lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'yknpg_api.dart';

Future<void> main() async {
  await dotenv.load(fileName: ".env");
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PaymentPage(),
    );
  }
}

class PaymentPage extends StatefulWidget {
  @override
  State<PaymentPage> createState() => _PaymentPageState();
}

class _PaymentPageState extends State<PaymentPage> {
  final _phoneCtrl = TextEditingController();
  final _amountCtrl = TextEditingController(text: '1000');
  String _status = '';
  bool _loading = false;

  Future<void> _submit() async {
    setState(() {
      _loading = true;
      _status = '';
    });
    try {
      final result = await startTransaction(
          int.parse(_amountCtrl.text), _phoneCtrl.text);
      setState(() {
        _status =
            "Gateway: ${result['created']['instructions']}\nPost-pay: ${result['paid']['instructions']}\nFinal status: ${result['finalStatus']}";
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
    } finally {
      setState(() {
        _loading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Start payment')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(controller: _phoneCtrl, decoration: const InputDecoration(labelText: 'Phone')),
            TextField(
              controller: _amountCtrl,
              decoration: const InputDecoration(labelText: 'Amount'),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _loading ? null : _submit,
              child: Text(_loading ? 'Processing...' : 'Pay'),
            ),
            const SizedBox(height: 16),
            Text(_status),
          ],
        ),
      ),
    );
  }
}
  1. Exécuter
  • flutter run

Remarques

  • Déplacez le jeton vers un backend sécurisé ou une gestion de clés pour la production.