Flutter

A complete guide to installing, initializing, and using the EdfaPay SoftPOS SDK in Flutter, including examples, theming, and Android setup notes.

Installation

Add the dependency to your project’s pubspec.yaml file:


dependencies:
edfapay_softpos_sdk: 1.0.0+3 # or use 'any' to always get the latest version

Important

Add below credentials properties to your project ./android/gradle.properties or ~/.gradle/gradle.properties

PARTNER_REPO_USERNAME=edfapay-sdk-consumer
PARTNER_REPO_PASSWORD=Edfapay@123

Important

Change the android MainActivity super class from FlutterActivity to FlutterFragmentActivity

package com.edfapay.sample_app // your application package

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity()

Usage

Imports

import 'package:edfapay_softpos_sdk/edfapay_softpos_sdk.dart';
import 'package:edfapay_softpos_sdk/models/edfapay_credentials.dart';
import 'package:edfapay_softpos_sdk/enums/flow_type.dart';
import 'package:edfapay_softpos_sdk/enums/purchase_secondary_action.dart';
import 'package:edfapay_softpos_sdk/enums/presentation.dart';
import 'package:edfapay_softpos_sdk/models/txn_params.dart';
import 'package:edfapay_softpos_sdk/enums/env.dart';
import 'package:edfapay_softpos_sdk/helpers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

1) Initialization

Create Credentials

Prompts the user to enter their Email and Password during SDK initialization.

EdfaPayCredentials credentials = EdfaPayCredentials.withEmailPassword(
  environment: Env.DEVELOPMENT,
  email: null,
  password: null,
);

Initialize with Created Credentials

This function will begin initialization and configure your phone for transactions.
Note: Make sure to catch exceptions and inspect the failure reason.

EdfaPayPlugin.initiate(credentials: credentials).then((result) {
  if (result.status == false) {
    // Handle initialization failure
    // Inform the developer/user and take necessary action
    // Check logs for detailed failure reason.
  } else {
    // Allow user to start payment process in next step
    EdfaPayPlugin.RemoteChannel.LocalNetwork(port: 8080, timeout: 3.0).open();

  }
}).catchError((e) {
  if (e is PlatformException) {
    toast(e.message ?? e.code);
  } else {
    toast("Error Initializing SDK: ${e.toString()}");
  }
});
❗️

Important Note

  • The Local Network listener must be started after successful SDK initialization
  • The selected port must be available and not used by another service
  • The minimum supported timeout is 3 minutes
  • This feature is optional and only required for multi-device payment scenarios

Set Theme (Optional)

Setting Colors and Logo

final logo = "base64 of image";
// Or use helper to encode an asset to base64:
// final logo = await assetsBase64('assets/images/your_logo.png');

EdfaPayPlugin.theme()
  .setPrimaryColor("#06E59F")
  .setSecondaryColor("#000000")
  .setPoweredByImage(logo)
  .setHeaderImage(logo);

Tip: You can convert an image asset to base64 using the SDK helper:

final logo = await assetsBase64('path/to/your/image_asset.png');

Setting Presentation

You can control how the payment UI is displayed using different presentation modes.

Available Presentation Modes: Presentation.FULLSCREEN,Presentation.DIALOG_CENTER,Presentation.DIALOG_TOP_FILL,Presentation.DIALOG_BOTTOM_FILL,Presentation.DIALOG_TOP_START,Presentation.DIALOG_TOP_END,Presentation.DIALOG_TOP_CENTER,Presentation.DIALOG_BOTTOM_START,Presentation.DIALOG_BOTTOM_END,Presentation.DIALOG_BOTTOM_CENTER

Note:
All presentation properties can be customized from their default values to enhance the user experience.

Example

EdfaPayPlugin.theme()
    .setPresentation(
        Presentation.DIALOG_CENTER
            .sizePercent(0.85) // Aligned to the screen smallest axis (0.20 to 1.0)
            .dismissOnBackPress(true)
            .dismissOnTouchOutside(true)
            .animateExit(true)
            .animateEntry(true)
            .dimBackground(true)
            .dimAmount(1.0) // Range: 0.0 to 1.0
            .marginAll(0) // Any numeric value
            .cornerRadius(20) // Any numeric value
            .marginHorizontal(10) // Any numeric value
            .marginVertical(10) // Any numeric value
            .setPurchaseSecondaryAction(PurchaseSecondaryAction.none)
    );

Secondary / Post Purchase Transaction Action

Defines the action that can be performed after a successful purchase.

Available Actions

  • PurchaseSecondaryAction.reverse
  • PurchaseSecondaryAction.refund
  • PurchaseSecondaryAction.none

Example

EdfaPayPlugin.theme()
    .setPresentation(
        Presentation.DIALOG_CENTER
            .setPurchaseSecondaryAction(PurchaseSecondaryAction.none)
    );

2) Transactions

A) Purchase

  • FlowType.immediate It will close the Card Scanning Interface immediately after receive response from server.
  • FlowType.status It will close the Card Scanning Interface at the status animation check/cross.
  • FlowType.detail It will take the UI flow up to transaction detail screen and will completed by closing screen/ui.

Note: Observe the 4th isFlowCompleted parameter of onPaymentProcessComplete callback.

EdfaPayPlugin.purchase(
    params,
    flowType: FlowType.detail,
    onPaymentProcessComplete: (status, code, result, isFlowCompleted){
        if(status){
            print(' >>> [ Success ]');
            print(' >>> [ ${jsonEncode(result)} ]');
        }else{
            print(' >>> [ Failed ]');
            print(' >>> [ ${jsonEncode(result)} ]');
        }
    },
    onServerTimeOut: (){
        print('>>> Server Timeout');
        print(' >>> The request timeout while performing transaction at backend');
    },
    onScanCardTimeOut: (){
        print('>>> Scan Card Timeout');
        print(' >>> The scan card timeout, no any card tap on device');
    },
    onCancelByUser: (){
        print('>>> Canceled By User');
        print(' >>> User have cancel the scanning/payment process on its own choice');
    },
    onError: (error){
        print('>>> Exception');
        print(' >>> "Scanning/Payment process through an exception, Check the logs');
        print('  >>> ${error.toString()}');
    }
);

B) Refund

EdfaPayPlugin.refund(
  params,
  onPaymentProcessComplete: (status, code, result, isProcessComplete) {
    toast("Refund Completed");
    print('>>> Refund Process Complete');
  },
  onServerTimeOut: () {
    toast("Server Request Timeout");
    print('>>> Server Timeout while refunding');
  },
  onScanCardTimeOut: () {
    toast("Card Scan Timeout");
    print('>>> Scan timeout (if flow requires tap)');
  },
  onCancelByUser: () {
    toast("Cancel By User");
    print('>>> User canceled the refund flow');
  },
  onError: (Exception error) {
    toast(error.toString());
    print('>>> Exception: ${error.toString()}');
  },
);

C) Reconciliation

EdfaPayPlugin.reconcile(
  onSuccess: (response) {
    toast("Success: Reconciliation");
    print('>>> Reconciliation Success');
  },
  onError: (error) {
    print('>>> Exception during reconciliation: ${error.toString()}');
  },
);

D) Transaction History

EdfaPayPlugin.txnHistory(
  onSuccess: (response) {
    toast("Success: Transaction History");
    print('>>> Txn History Success');
  },
  onError: (error) {
    print('>>> Exception fetching history: ${error.toString()}');
  },
);

Example

Instruction to Run Example

// ignore_for_file: avoid_print
import 'package:edfapay_softpos_sdk/edfapay_softpos_sdk.dart';
import 'package:edfapay_softpos_sdk/enums/flow_type.dart';
import 'package:edfapay_softpos_sdk/enums/presentation.dart';
import 'package:edfapay_softpos_sdk/enums/purchase_secondary_action.dart';
import 'package:edfapay_softpos_sdk/models/edfapay_credentials.dart';
import 'package:edfapay_softpos_sdk/models/txn_params.dart';
import 'package:edfapay_softpos_sdk/enums/env.dart';
import 'package:edfapay_softpos_sdk/helpers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/* add the plugin for below: (hexcolor: any) https://pub.dev/packages/hexcolor */
import 'package:hexcolor/hexcolor.dart';

// add the plugin for below: (fluttertoast: any) https://pub.dev/packages/fluttertoast
import 'package:fluttertoast/fluttertoast.dart';



const TERMINAL_TOKEN = "D08BE4C0FE041A155F0028C0FCD042087771DA505D54087EFC3A0FC1183213D6";
const logoPath = "assets/images/edfa_logo.png";
const amountToPay = "01.010";

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var _edfaPluginInitiated = false;

  @override
  void initState() {
    super.initState();
    // initiate();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(15),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              const SizedBox(height: 20),
              Expanded(
                flex: 2,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    FractionallySizedBox(widthFactor: 0.3, child: Image.asset(logoPath)),
                    const SizedBox(height: 30),
                    const Text("SDK", style: TextStyle(fontSize: 65, fontWeight: FontWeight.w700), textAlign: TextAlign.center),
                    const SizedBox(height: 10),
                    const Text("v0.0.1", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
                  ],
                ),
              ),
              const Expanded(
                flex: 1,
                child: Padding(
                  padding: EdgeInsets.all(10),
                  child: Text("You're on your way to enabling your Android App to allow your customers to pay in a very easy and simple way just click the payment button and tap your payment card on NFC enabled Android phone.",
                      style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black45), textAlign: TextAlign.center),
                ),
              ),

              if(!_edfaPluginInitiated)
                ElevatedButton(onPressed: initiate, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Initiate", style: TextStyle(color: Colors.black))),

              if(_edfaPluginInitiated)
                ...[
                  ElevatedButton(onPressed: pay, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Pay $amountToPay", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: refund, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Refund With RRN", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: reconcile, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Reconciliation", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: txnHistory, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Txn History $amountToPay", style: TextStyle(color: Colors.black))),
                ],

              const Padding(
                padding: EdgeInsets.symmetric(horizontal: 10),
                child: Text("Click on button above to test the card processing with 10.00 SAR", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center),
              ),
            ],
          ),
        ),
      ),
    );
  }

  initiate() async {
    EdfaPayPlugin.enableLogs(true);
    /*
    EdfaPayCredentials credentials = EdfaPayCredentials.withEmailPassword(
        environment: Env.DEVELOPMENT,
        email: null,
        password: null
    );

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmailPassword(
        environment: Env.DEVELOPMENT,
        email: "[email protected]",
        password: "Password@123"
    );

    EdfaPayCredentials credentials = EdfaPayCredentials.withInput(
        environment: Env.PRODUCTION,
    );

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmail(
        environment: Env.DEVELOPMENT,
        email: "[email protected]",
    );

    EdfaPayCredentials credentials = EdfaPayCredentials.withToken(
        environment: Env.DEVELOPMENT,
        token: TERMINAL_TOKEN,
    );
    */

    EdfaPayCredentials credentials = EdfaPayCredentials.withToken(
      environment: Env.DEVELOPMENT,
      token: TERMINAL_TOKEN,
    );

    setTheme();
    EdfaPayPlugin.initiate(credentials: credentials).then((value) {
      setState(() {
        _edfaPluginInitiated = value.status;
      });

    }).catchError((e) {
      if (e is PlatformException) {
        toast(e.message ?? e.code);
      } else {
        toast("Error Initializing SDK: ${e.toString()}");
      }
    });
  }

  setTheme() async {
    final logo = await assetsBase64(logoPath);
    final presentation = Presentation.DIALOG_CENTER
        .sizePercent(0.85)
        .dismissOnTouchOutside(true)
        .dimBackground(true)
        .dimAmount(1.0)
        .marginAll(0)
        .animateEntry(true)
        .animateExit(true)
        .cornerRadius(20)
        .dismissOnBackPress(true)
        .marginHorizontal(10)
        .marginVertical(10)
        .setPurchaseSecondaryAction(PurchaseSecondaryAction.reverse);
    EdfaPayPlugin.theme()
        .setButtonBackgroundColor("#06E59F")
        .setButtonTextColor("#000000")
        .setHeaderImage(logo)
        .setPoweredByImage(logo)
        .setPresentation(presentation);
  }

  pay() async {
    if (!_edfaPluginInitiated) {
      toast("Edfapay plugin not initialized.");
      return;
    }

    final params = TxnParams.purchase(
        amount: amountToPay,
        orderId: "12340987"
    );

    EdfaPayPlugin.enableLogs(true);
    EdfaPayPlugin.purchase(params, flowType: FlowType.detail, onPaymentProcessComplete: (status, code, result, isFlowCompleted) {
      toast("Card Payment Process Completed");
      print('>>> Payment Process Complete');
    }, onServerTimeOut: () {
      toast("Server Request Timeout");
      print('>>> Server Timeout');
      print(' >>> The request timeout while performing transaction at backend');
    }, onScanCardTimeOut: () {
      toast("Card Scan Timeout");
      print('>>> Scan Card Timeout');
      print(' >>> The scan card timeout, no any card tap on device');
    }, onCancelByUser: () {
      toast("Cancel By User");
      print('>>> Canceled By User');
      print(' >>> User have cancel the scanning/payment process on its own choice');
    }, onError: (Exception error) {
      toast(error.toString());
      print('>>> Exception');
      print(' >>> "Scanning/Payment process through an exception, Check the logs');
      print('  >>> ${error.toString()}');
    });
  }

  refund() async {
    if (!_edfaPluginInitiated) {
      toast("Edfapay plugin not initialized.");
      return;
    }

    final params = TxnParams.refundWithRrn(
      amount: amountToPay,
      transactionDate: DateTime(2025, 08, 26),
      rrn: null,
    );

    EdfaPayPlugin.enableLogs(true);
    EdfaPayPlugin.refund(params, onPaymentProcessComplete: (status, code, result, isFlowCompleted) {
      toast("Card Payment Process Completed");
      print('>>> Payment Process Complete');
    }, onServerTimeOut: () {
      toast("Server Request Timeout");
      print('>>> Server Timeout');
      print(' >>> The request timeout while performing transaction at backend');
    }, onScanCardTimeOut: () {
      toast("Card Scan Timeout");
      print('>>> Scan Card Timeout');
      print(' >>> The scan card timeout, no any card tap on device');
    }, onCancelByUser: () {
      toast("Cancel By User");
      print('>>> Canceled By User');
      print(' >>> User have cancel the scanning/payment process on its own choice');
    }, onError: (Exception error) {
      toast(error.toString());
      print('>>> Exceptio¬n');
      print(' >>> "Scanning/Payment process through an exception, Check the logs');
      print('  >>> ${error.toString()}');
    });
  }


  reconcile() async {
    if (!_edfaPluginInitiated) {
      toast("Edfapay plugin not initialized.");
      return;
    }

    EdfaPayPlugin.reconcile(
        onSuccess: (response){
          toast("Success reconcile");
        },
        onError: (error){
          print('>>> Exception');
          print(' >>> "Scanning/Payment process through an exception, Check the logs');
          print('  >>> ${error.toString()}');
        }
    );
  }


  txnHistory() async {
    if (!_edfaPluginInitiated) {
      toast("Edfapay plugin not initialized.");
      return;
    }

    EdfaPayPlugin.txnHistory(
        onSuccess: (response){
          toast("Success txnHistory");
        },
        onError: (error){
          print('>>> Exception');
          print(' >>> "Scanning/Payment process through an exception, Check the logs');
          print('  >>> ${error.toString()}');
        }
    );
  }

  toast(String text) {
    Fluttertoast.showToast(msg: text);
  }

}

Important Notes

  1. SoftPOS service is currently supported on Android devices only.
  2. FlutterFragmentActivity is mandatory on Android (extend it if you embed Flutter into an existing app).
  3. NFC must be enabled on the device.
  4. Terminal Token initialization is recommended for production (silent, no user prompts).
  5. RRN is returned only after a successful purchase and must be stored for future refunds.
  6. Reconciliation and Refund features must be enabled from backend.

📘The official package page

https://pub.dev/packages/edfapay_softpos_sdk

contains the latest SDK version, change log, and example code. Always refer to it before upgrading or integrating.