Guide

Flutter guide for running the YKNPG transaction flow from a mobile app (development demo).

Flutter guide for running the YKNPG transaction flow from a mobile app (development demo).

Security note

  • Store the bearer token securely (e.g., remote config or a backend proxy). Using .env in-app is for local testing.

Prerequisites

  • Flutter SDK, Dart 3+.
  1. Create app
  • flutter create demo_yknpg && cd demo_yknpg
  1. Add dependencies
  • flutter pub add http flutter_dotenv
  1. Add environment file
  • File: .env
YKNPG_API_TOKEN=replace-with-your-token
YKNPG_BASE_URL=https://yknpg.ngoul.com/api/v1
  • Update pubspec.yaml to include the file:
flutter:
  assets:
    - .env
  1. Create API helper
  • File: 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. Update UI
  • File: 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. Run
  • flutter run

Notes

  • Move token to a secure backend or key management for production.