DocsChangelog
API Reference

PayWidget

Drop-in Flutter widget for the full FedaPay payment flow.

Overview

PayWidget is a drop-in Flutter widget that embeds the FedaPay payment page. It handles transaction creation (optional), URL construction, and payment callbacks automatically. It works across Mobile (WebView) and Web/Desktop (Browser redirect).

Usage

Simple Usage (Token-based)

If you already have a transaction token from your backend:

PayWidget(
  transactionToken: 'ts_token_xxx',
  onPaymentSuccess: () => print('Success'),
  onPaymentFailed: () => print('Failed'),
)

Advanced Usage (Client-side creation)

If you want the widget to create the transaction for you:

PayWidget(
  instance: FedaFlutter.instance,
  amount: 2000,
  description: 'Order #123',
  customer: {'email': 'test@example.com'},
  onPaymentSuccess: () => print('Success'),
  onPaymentFailed: () => print('Failed'),
)

Parameters

ParameterTypeRequiredDescription
onPaymentSuccessVoidCallbackCalled on successful payment
onPaymentFailedVoidCallbackCalled on failed payment
transactionTokenString?FedaPay transaction token (skips creation)
instanceFedaFlutter?SDK instance (required for client-side creation)
amountint?Amount in local currency subunit
currencyCurrencyIso?Currency (defaults to XOF)
customerMap<String, dynamic>?Customer data (email, firstname, etc.)
  • Mobile (Android/iOS): Opens the FedaPay checkout in a secure embedded WebView. It automatically detects redirection to /status/success or /status/failure.
  • Web & Desktop: Displays a "Pay" button that opens the checkout in a new browser tab/window. To ensure the application is notified of the payment status, a polling mechanism is used.

!NOTE For polling to work on Desktop, you must provide the instance (or use the client-side creation flow). If you only provide a transactionToken, the application won't be able to automatically poll for the status.

How it works

  1. If transactionToken is provided, it constructs the URL: https://checkout.fedapay.com/pay/{token}.
  2. If not, it uses the provided instance to create a transaction via the API first.
  3. On Mobile, it loads the URL in a WebView and monitors URL changes.
  4. On Web/Desktop, it uses url_launcher to redirect the user and starts polling the transaction status every 3 seconds until the payment is approved, canceled, or declined.
  5. Once the status is confirmed by the API, the corresponding onPaymentSuccess or onPaymentFailed callback is triggered.

This is the most secure way to integrate FedaPay. Your secret API keys are safely stored on your backend (e.g. using ashgateway), and the mobile app communicates via a secure proxy.

import 'package:feda_flutter/feda_flutter.dart';
import 'package:flutter/material.dart';

void main() {
  // Initialize with your public project key and your cloud proxy URL
  FedaFlutter.applyCloudConfig(
    projectKey: 'your_public_project_key',
    cloudUrl: 'https://your-ashgateway-instance.com',
  );

  runApp(const MyApp());
}

Direct API Example (Testing & Prototyping)

Here is a concrete example of a checkout page using the simplified PayWidget. This example shows how to handle the payment state and show success/failure feedback using a SnackBar.

import 'package:feda_flutter/feda_flutter.dart';
import 'package:flutter/material.dart';

void main() {
  FedaFlutter.applyConfig(
    environment: ApiEnvironment.sandbox,
    apiKey: 'test_api_key_1234567890abcdef',
  );

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Checkout Demo',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple)),
      home: const MyHomePage(title: 'FedaPay Checkout'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _showPayment = false;

  void _startPayment() {
    setState(() {
      _showPayment = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: _showPayment
            ? PayWidget(
                instance: FedaFlutter.instance,
                amount: 5000,
                description: 'Demo payment from Lab App',
                customer: const {
                  'email': 'customer@example.com',
                  'firstname': 'John',
                  'lastname': 'Doe',
                },
                onPaymentSuccess: () {
                  setState(() => _showPayment = false);
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Paiement réussi !')),
                  );
                },
                onPaymentFailed: () {
                  setState(() => _showPayment = false);
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Paiement échoué.')),
                  );
                },
              )
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(
                    Icons.shopping_cart_outlined,
                    size: 100,
                    color: Colors.grey,
                  ),
                  const SizedBox(height: 20),
                  const Text(
                    'Votre panier est prêt',
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  const Text('Total: 5000 FCFA'),
                  const SizedBox(height: 30),
                  ElevatedButton.icon(
                    onPressed: _startPayment,
                    icon: const Icon(Icons.payment),
                    label: const Text('Procéder au paiement'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 32,
                        vertical: 16,
                      ),
                    ),
                  ),
                ],
              ),
      ),
    );
  }
}