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
ImportantAdd below credentials properties to your project
./android/gradle.propertiesor~/.gradle/gradle.propertiesPARTNER_REPO_USERNAME=edfapay-sdk-consumer PARTNER_REPO_PASSWORD=Edfapay@123
ImportantChange 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.reversePurchaseSecondaryAction.refundPurchaseSecondaryAction.none
Example
EdfaPayPlugin.theme()
.setPresentation(
Presentation.DIALOG_CENTER
.setPurchaseSecondaryAction(PurchaseSecondaryAction.none)
);2) Transactions
A) Purchase
FlowType.immediateIt will close the Card Scanning Interface immediately after receive response from server.FlowType.statusIt will close the Card Scanning Interface at the status animation check/cross.FlowType.detailIt will take the UI flow up to transaction detail screen and will completed by closing screen/ui.
Note: Observe the 4th
isFlowCompletedparameter ofonPaymentProcessCompletecallback.
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
- Make sure to install these plugins in your sample Flutter app:
- Don’t forget to apply the Important configuration/changes at the top of this page to your project.
- Copy the below content and paste it into
main.dart(replace all content).
// 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
- SoftPOS service is currently supported on Android devices only.
- FlutterFragmentActivity is mandatory on Android (extend it if you embed Flutter into an existing app).
- NFC must be enabled on the device.
- Terminal Token initialization is recommended for production (silent, no user prompts).
- RRN is returned only after a successful purchase and must be stored for future refunds.
- Reconciliation and Refund features must be enabled from backend.
The official package pagehttps://pub.dev/packages/edfapay_softpos_sdk
contains the latest SDK version, change log, and example code. Always refer to it before upgrading or integrating.
Updated 8 days ago