DEV Community

Cover image for How to Integrate Flutterwave Payment Gateway into your Flutter App

How to Integrate Flutterwave Payment Gateway into your Flutter App

A payment gateway is a digital service that enables businesses to accept, process, and manage transactions securely. It acts as a bridge between your app and financial institutions, making sure fund transfers are smooth and safe.

In this guide, you’ll learn how to integrate payment into your Flutter application using Flutterwave Standard, but before building the application, let’s explore Flutterwave Standard and its advantages.

What is Flutterwave Standard?

Flutterwave Standard is a payment solution that helps businesses accept payments easily without dealing with complicated processing steps. It uses a redirect-based checkout, meaning customers are sent to a secure Flutterwave page to pay and then returned to the business’s website or app.

Key Features of Flutterwave Standard:

  • Multiple Payment Options: Supports cards, bank transfers, mobile money (MPesa, Airtel Money), USSD, and digital wallets (Apple Pay and Google Pay).
  • Safe Transactions: Uses strong security measures to protect payments.
  • Easy to Use: Businesses only need to send payment details through an API, and Flutterwave handles everything.
  • Constantly Updated: New payment methods and security improvements happen automatically.
  • Custom Checkout: Businesses can add their branding to the payment page.

How Flutterwave Standard Works:

How Flutterwave Standard works

  1. The merchant initiates a payment request via API, providing transaction details.
  2. The user is redirected to the Flutterwave-hosted checkout page.
  3. The user selects a payment method and completes the transaction.
  4. Flutterwave processes the payment and redirects the user to the merchant’s app with the transaction status.

With that done, let’s get started building the application.

Step 1: Project Set Up

To get started, use your terminal to create a new Flutter project.

flutter create flutter_flw && cd flutter_flw
Enter fullscreen mode Exit fullscreen mode

Next, install the project dependencies by updating the dependencies section in the pubspec.yaml file.

dependencies:
    # Other dependencies go here
    flutter_dotenv: ^5.2.1
    webview_flutter_wkwebview: ^3.17.0
    http: ^1.3.0
Enter fullscreen mode Exit fullscreen mode

Let’s look at what these dependencies are and what they are used for:

flutter_dotenv is a package for loading environment variables.

webview_flutter_wkwebview is a package for embedding web view (browser capabilities).

HTTP is a package for making HTTP requests.

Get the API key and Configure the Environment Variables

First, log into your Flutterwave dashboard and navigate to the API keys section under the Settings menu.

Get API key

Secondly, create a .env file in the root of your project and add the snippet below:

FLUTTERWAVE_SECRET_KEY=<REPLACE WITH YOUR SECRET KEY>
FLUTTERWAVE_BASE_URL=https://api.flutterwave.com/v3
Enter fullscreen mode Exit fullscreen mode

Thirdly, update the assets section of the pubspec.yaml file so that your Flutter application can read the environment variables.

assets:
      - .env
Enter fullscreen mode Exit fullscreen mode

Finally, load the .env file in the main.dart file.

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

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

// other parts of the main.dart go here.
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Flutterwave Service Helper

Now that it’s set up, the next step is to create a service file to integrate Flutterwave's payment gateway into your app. To do this, create a flutterwave_service.dart file inside the lib directory and add the following code snippet:

import 'dart:convert';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;

class FlutterwavePayment {
  static final String _secretKey = dotenv.get("FLUTTERWAVE_SECRET_KEY");
  static final String _baseUrl = dotenv.get("FLUTTERWAVE_BASE_URL");

  // Initialize payment
  static Future<String> initializePayment({
    required String email,
    required double amount,
    required String currency,
    required String txRef,
  }) async {
    final url = Uri.parse('$_baseUrl/payments');
    final response = await http.post(
      url,
      headers: {
        'Authorization': 'Bearer $_secretKey',
        'Content-Type': 'application/json',
      },
      body: jsonEncode({
        'tx_ref': txRef,
        'amount': amount.toString(),
        'currency': currency,
        'payment_options': 'card, mobilemoney, ussd',
        'redirect_url':
            'https://your-callback-url.com/success', // Your dummy callback URL
        'customer': {
          'email': email,
        },
      }),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return data\['data'\]['link']; // Payment link
    } else {
      throw Exception('Failed to initialize payment');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Imports the required dependencies.
  • Defines the FlutterwavePayment class, which retrieves the secret key and base URL from the environment variables file.
  • Adds an initializePayment method (async) that accepts parameters ( email, amount, currency, and txRef).
  • Sends a payment request to the Flutterwave Standard endpoint with multiple payment options, a dummy redirect_url, and processes the response accordingly.

redirect_url is the URL where users are redirected after completing or canceling a payment on the Flutterwave-hosted checkout page. In this case, a dummy URL is used because Flutter apps do not rely on schematic URLs for navigation but instead manage screens within the app.

Step 3: Configure Web View to Load the Checkout Page

Since Flutterwave Standard generates a URL with a checkout page for users to complete their payment, you need to configure Flutter to load this page. To do this, create a payment_webview.dart file and add the following snippet:

import 'package:flutter/material.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

class PaymentWebView extends StatefulWidget {
  final String paymentUrl;
  const PaymentWebView({super.key, required this.paymentUrl});
  @override
  State<PaymentWebView> createState() => PaymentWebViewState();
}

class PaymentWebViewState extends State<PaymentWebView> {
  late final PlatformWebViewController _controller;
  @override
  void initState() {
    super.initState();
    final PlatformWebViewControllerCreationParams params =
        WebKitWebViewControllerCreationParams(
      allowsInlineMediaPlayback: true,
      mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
    );
    _controller = PlatformWebViewController(params)
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(
        LoadRequestParams(
          uri: Uri.parse(widget.paymentUrl),
        ),
      )
      ..setPlatformNavigationDelegate(
        PlatformNavigationDelegate(
          const PlatformNavigationDelegateCreationParams(),
        )..setOnNavigationRequest(
            (NavigationRequest request) async {
              // Handle payment success/failure
              if (request.url
                  .contains('https://your-callback-url.com/success')) {
                // Other Verification logic from your backend 
                Navigator.pop(context, 'success');
              } else if (request.url
                  .contains('https://your-callback-url.com/failure')) {
                Navigator.pop(context, 'failure');
              }
              return NavigationDecision.navigate;
            },
          ),
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Complete Payment')),
      body: PlatformWebViewWidget(
        PlatformWebViewWidgetCreationParams(controller: _controller),
      ).build(context),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The snippet above creates a PaymentWebView class that accepts a payment URL and loads it using the webview_flutter_wkwebview package. It also performs checks to determine if the returned URL, after the payment is initiated, contains the redirect_url specified earlier.

Step 4: Putting it Together

Next, update the main.dart file to use the FlutterwavePayment service and the PaymentWebView to make payment.

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_flw/flutterwave_service.dart';
import 'package:flutter_flw/payment_webview.dart';

Future 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(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  void _makePayment() async {
    try {
      final paymentLink = await FlutterwavePayment.initializePayment(
        email: 'user@example.com',
        amount: 100.0,
        currency: 'NGN',
        txRef:
            'UNIQUE_TRANSACTION_REF_${DateTime.now().millisecondsSinceEpoch}',
      );
      // Open WebView
      final result = await Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => PaymentWebView(paymentUrl: paymentLink),
        ),
      );
      if (result == 'success') {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Payment successful')),
        );
      } else if (result == 'failure') {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Payment failed')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
                style: TextButton.styleFrom(
                  backgroundColor: Colors.black,
                ),
                onPressed: _makePayment,
                child: Text("Make Payment of #100",
                    style: TextStyle(color: Colors.white))),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

With that done, start the application using the code editor or run the command below.

flutter run
Enter fullscreen mode Exit fullscreen mode

You can test a card payment with the details below or use any of these testing credentials to simulate other payment methods.

Name Details
Card Number 5061460166976054667
Expiry Date 10/29
CVV 564
Pin 3310

https://www.dropbox.com/scl/fi/hbhce1fdi24z9qehzep1o/Screen-Recording-2025-02-01-at-03.32.06.mov?rlkey=o3erd3f7pth62jazpb7kzg3c8&st=06xehajs&dl=0

As a best practice, always verify a transaction on the server before providing value to your user. Flutterwave offers multiple backend SDKs to make this verification seamless.

The complete source code can be found on GitHub.

Wrapping up

This guide covers how to integrate Flutterwave Standard into your Flutter app, enabling you to accept payments securely. Check out the documentation to learn more about Flutterwave Standard and how to handle additional edge cases.

Top comments (0)