Revamped Flutter Edfapay SoftPOS SDK

Helps developers to easly integrate EdfaPay SoftPos to flutter mobile application with few simple steps.

Wiki & Documentation

EdfaPay SoftPos SDK

Important

Install edfapay-softpos-sdk

  • Add the dependency in to your project by running terminal command pub add at project directory.
flutter pub add edfapay_softpos_sdk
  • or add the dependency in to project pubspec.yaml
dependencies:
edfapay_softpos_sdk: 1.0.0+1 # or specify 'any' to always look for the latest version

Important

Set Repository Access Credentials

  • 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

FlutterFragmentActivity

  • Change the android MainActivity super class from FlutterActivity to FlutterFragmentActivity
  • Path: At project directory: ./android/app/src/main/kotlin/app_package_tree/MainActivity.kt

Example

package com.edfapay.sample_app // your application package

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity()

Usage

1: Imports

  • All files under package package:edfapay_softpos_sdk you need to import to your integration class.
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';

2: Setting Theme (Optional)

2.1: Setting Colors and Logos

final logo = "base64 of image";
// final logo = await assetsBase64('path to image asset');

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

Tip

There is an helper method in SDK to convert image asset to base64

final logo = await assetsBase64('path to image asset');

2.2: Setting Presentation

  • 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: Properties of each presentation can be change from default to change User Experience

// Usage:
EdfaPayPlugin.theme()
    .setPresentation(
        Presentation.DIALOG_CENTER
            .sizePercent(0.85) // aligned to screen smallest axis 0.20 to 1.0
            .dismissOnBackPress(true)
            .dismissOnTouchOutside(true)
            .animateExit(true)
            .animateEntry(true)
            .dimBackground(true)
            .dimAmount(1.0) // 0.0 to 1.0
            .marginAll(0) // open number
            .cornerRadius(20)  // open number
            .marginHorizontal(10)  // open number
            .marginVertical(10)  // open number
            .setPurchaseSecondaryAction(PurchaseSecondaryAction.none)
);

2.3: Secondary/Post Purchase Transaction Action

  • PurchaseSecondaryAction.reverse
  • PurchaseSecondaryAction.refund
  • PurchaseSecondaryAction.none
// Usage:
EdfaPayPlugin.theme()
    .setPresentation(
        Presentation.DIALOG_CENTER
            .setPurchaseSecondaryAction(PurchaseSecondaryAction.none)
    );

2.4: Enable or disable PIN PAD shuffle at PIN Transaction

// Usage:
EdfaPayPlugin.theme()
  .setPresentation(
      Presentation.DIALOG_CENTER
          .setShufflePinPad(true); // default false
  );

3: Initialization

3.1: Create Credentials

  • I will prompt for the credentials whether user Email/Password or Token

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmailPassword(
        environment: Env.DEVELOPMENT,
        email: null,
        password: null
    );
    
  • I will also prompt for the credentials whether user Email/Password or Token

    EdfaPayCredentials credentials = EdfaPayCredentials.withInput(
        environment: Env.PRODUCTION,
    );
    
  • I will prompt for the credentials with prefilled user Email/Password

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmailPassword(
        environment: Env.DEVELOPMENT,
        email: "user@email.com",
        password: "Password@123"
    );
    
  • I will prompt for the credentials with prefilled user Email

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmail(
        environment: Env.DEVELOPMENT,
        email: "user@email.com",
    );
    
  • I will silently prepare and initialize the SDK with prefilled terminal Token

    • This should be Terminal Token to behave as silent SDK initialization
    EdfaPayCredentials credentials = EdfaPayCredentials.withToken(
         environment: Env.DEVELOPMENT,
         token: "****Terminal Token Should be Generated at Edfapay SoftPOS Portal****",
    );
    

3.2: Initialize with Created Credentials

  • This function will begin initialization and configure your phone for transactions.
    • Note: Make sure to catch the exception and observe the reason of failed initialization.
      EdfaPayPlugin.initiate(
          credentials: credentials,
          onError: (e){
            print('** Error Initializing SDK **');
            print('>>> Error');
            print('  >>> ${jsonEncode(e)}');
              
          },
          onTerminalBindingTask: (bindingTask){
    
            print('>>> Terminal Binding Required');
    
            List<Terminal> terminals = bindingTask.terminals;
    
            // Binding Option 1: Show SDK's built-in terminal selection UI
            bindingTask.bind();
    
            // Binding Option 2: Bind programmatically or InApp UI list
            terminals.firstOrNull?.bind();
    
            // Refresh terminal list, then show custom UI
            bindingTask.refresh((error, data){
              if(error != null){
                  
                print('** Error Refreshing Terminal List **');
                print('>>> Error');
                print('  >>> ${jsonEncode(error)}');
                  
              }else if (data != null){
                  
                print('** Success Refreshing Terminal List **');
                print('>>> Success');
                print('  >>> ${jsonEncode(data)}');
                  
              }
            });
          },
          onSuccess: (sessionId){
              
            print('** Successful Initialized SDK **');
            print('>>> Success');
            print('  >>> Session ID: $sessionId');
              
            setState(() {
              _edfaPluginInitiated = true;
            });
          }
      );
    

3.3: Enable start transaction from remote source Optional

  • Make sure to open RemoteChannel after successful SDK initialization.
    // Usage: Make sure to open RemoteChannel after initialize successful 
    EdfaPayPlugin.RemoteChannel.LocalNetwork(8080, 3.0).open();
    
  • Request from remote source:
     # This command passing json to local IP and port of device running our SDK, via Netcat nc 
     echo '{"id":"pay.1","data":{"fun":"purchase","flowType":"DETAIL","amount":"12.0"}}' | nc 192.168.100.70 8080
    
  • Remote Source will receive response in json.
    // This json may receive multiple time depends on FlowType: 
    // IMIDIATE:1time | STATUS:2time | DETAIL:3time)
    // Developer have to observe the {{json}}.data.flowComplete
    {"id":"pay.1","status":"Status.Approved","data":{"status":true,"code":"0000","transaction":{"acquirer_bank":"RAJB","mada_merchant_id":"800150400566","mada_terminal_id":"5116203000000000","amount":"12.00","apk_version_no":"1.0.0","application_cryptogram":"97FA72F45E2921A9","application_id":"A0000002281010","auth_code":"963680","bid":"RAJB","card_acceptor_business_code":"7399","card_expiration_date":"2902","credit_number":"5069 68** **** 0286","credit_number_length":4,"cvm":"3F0000","cardholder_name":"202F","created_date":"2026-02-18T17:18:40.883889000","cryptogram_information_data":"80","currency":"682","cvmr":{"cvmr_code":"3","cvmr_message_arabic":"لا يتطلب التحقق","cvmr_message_english":"NO VERIFICATION REQUIRED"},"downloadParameter":0,"finish_date":"2026-02-18T17:18:40.900968079","forceReconciliation":0,"formatted_created_date":"18/02/2026","formatted_created_time":"17:18:40","formatted_finish_date":"18/02/2026","formatted_finish_time":"17:18:40","kernel_id":"02","location":"null/null","merchant_id":"cf6858e4-8396-46db-880f-2ca9cc0335ee","operation_type":"PURCHASE","qr_data":"MTI 1200 PCODE 000000 TXNAMT 000000001200 STAN 000212 L-DATE\u0026TIME 260218171840 BID RAJB SCHEME P1 TID 5116203000000000 MID 800150400566 POSCODE 71030170333C FCODE 200 MRCODE 1990 MCC 7399 RRN 181418400212 APPCODE 963680 ACTCODE 000 9F24 3233523033313038334848384149523255373550413158495039323449 9F25 0544","response_message_code":"000","response_message_text_english":"APPROVED","response_message_text_arabic":"مقبولة","rrn":"181418400212","scheme":"P1","stan":"000212","status":"APPROVED","tag9F24":"3233523033313038334848384149523255373550413158495039323449","tag9F25":"0544","tvr":"0480000000","transaction_number":"8d174e11-2a5a-48c6-84c1-d1cb8871ce7c","transaction_status_info":"0000","tsn":"102030","request":{"22":"710101711334","23":"000","47":"RAJBP1","cryptogram_data":"80","26":"7399","acquirer_bank":"RAJB","terminalCurrencyCode":"682","merchant_id":"800150400566","outlet_name":"","tsn":"102030","cvmr_code":"3","cardAcceptorNameLocation":"SULAIMAN ALSAID CENTER FOR HOUSE\\KING KHALIED ROAD\\\\","pos_sn":"ffffffff-8356-b142-ffff-ffff8356b142","cvm":"3F0000","terminal_id":"5116203000000000","53":"51FFFF10203000000002","32":"400022","cryptogram":"97FA72F45E2921A9","55":"BAF4A0F13C76E75FF6A5DA05BA8B96B24A3D823D3768DC2B19FD47ECE087780EA09945B21B4DFCC8661E69AB760B243815C5B5C05D15C0A259D548C8710BD8CFEAB4325E4C1010B2F7E771ED8347C36EF7B0CFF0A8C3ED2AF9961EC1D98B6932B8042C228EBBC28EB9665F3ACF77220D89B97F2CF20A60F678C82509304C169E85D6CE806D1858ACF7B131D454F3AA3AB6CC6478BCF2AAE9ECFA70DABF989DF9D7915BBFDD0B50463EB1FCBDB0F6A459A065A09694BA83D4D0E47158C110FBD3BA5FC1CCCDD24D7B224E61CAC8D968CC94DE1D06C5A65D3DA66A0D8F697650D49298B517D6751D4AE386DC8EFBE500F18BDFC361A05A240C6D099F96BFB0808D","12":"2026-02-18 17:18:41 GMT+03:00","transportData":"002004TMS2003004000000400200006001S","35":"1003A1C26FF6D281B2874A1406BDA85C7421EA76987AC91313C975E412C174C2","transaction_status_info":"0000","device_id":"ffffffff-8356-b142-ffff-ffff8356b142","kernel":"02","cardholder_name":"202F","version":"1.0.0","tvr":"0480000000","4":"12.00","outlet_id":"","business_unit_name":"Zohaib Kambrani 102030","61":"8d174e11-2a5a-48c6-84c1-d1cb8871ce7c","bid":"cf6858e4-8396-46db-880f-2ca9cc0335ee","62":{"11":"000000000","12":"170263041","02":"0","13":"000000000","03":"000000","04":"1","05":"1","16":"01020202","09":"0","location_in_utm_format":"59N244659E0464222","long":46.7061832,"lat":24.7830929,"10":"000000000"},"63":"us-en","41":"5116203000000000","order_id":"8d174e11-2a5a-48c6-84c1-d1cb8871ce7c","aid":"A0000002281010","application_label":"mada"}},"flowComplete":true}}
    

4: 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.

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

        EdfaPayPlugin.purchase(
            txnParams: params,
            flowType: FlowType.DETAIL,
            onPaymentProcessComplete: (status, code, result, isFlowCompleted) {
              if(status){
                print(' >>> [ Success ]');
                print(' >>> [ ${jsonEncode(result)} ]');
              }else{
                print(' >>> [ Failed ]');
                print(' >>> [ ${jsonEncode(result)} ]');
              }
            }, 
            onRequestTimerEnd: () {
              print('>>> Server Timeout');
              print(' >>> The request timeout while performing transaction at backend');
            },
            onCardScanTimerEnd: () {
              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('>>> Error');
              print(' >>> "Scanning/Payment process through an error');
              print('  >>> ${jsonEncode(error)}');
        });

5: Invoice Payment & Refund (BNPL / Checkout)

Use this when the customer pays through a hosted checkout (BNPL providers like Tamara/Tabby, or a payment-gateway link) instead of tapping a card. invoicePayment returns a checkoutDeeplink you open in a browser/WebView so the customer can complete payment; invoiceRefund reverses a completed invoice by its transactionId.

Why a deeplink? These flows are hosted by the provider, so the SDK hands you a URL to redirect to rather than driving the card UI itself.

import 'package:edfapay_softpos_sdk/models/invoice_response.dart';

EdfaPayPlugin.invoicePayment(
  request: InvoicePaymentRequest(
    paymentMethod: PaymentMethod.tamara, // tamara | tabby | checkout
    merchantId: 'your-merchant-id',
    amount: 11100,                       // minor units (e.g. 111.00)
    currency: 'SAR',
    phoneNumber: '+966512345678',
    email: 'customer@example.com',
    orderReferenceId: 'ORD-111',
    orderNumber: '10111',
    locale: 'ar_SA',
    paymentType: 'PAY_BY_INSTALMENTS',
    invoice: Invoice(
      total: 11100,
      lineItems: [
        InvoiceLineItem(sku: 'SKU-001', description: 'Product A', unitCost: 11100, quantity: 1, netTotal: 11100, total: 11100),
      ],
    ),
    additionalData: AdditionalData(storeId: 'STORE-01'),
  ),
  onSuccess: (InvoicePaymentResponse res) {
    // Open res.checkoutDeeplink for the customer to finish paying
    print('Deeplink: ${res.checkoutDeeplink}, txn: ${res.transactionId}');
  },
  onError: (e) => print('Error: ${e['message']}'),
);

// Refund a completed invoice by its transaction id
EdfaPayPlugin.invoiceRefund(
  request: InvoiceRefundRequest(paymentMethod: PaymentMethod.tamara, amount: 11100, transactionId: 'txn-id'),
  onSuccess: (res) => print('Refunded: ${res.transactionId}'),
  onError: (e) => print('Error: ${e['message']}'),
);

6: Transaction Search & Gateway Status

searchTxnApi queries the backend for transactions (useful for reconciliation screens or lookups when you don't have a local record), and pgTxnStatusApi polls the payment-gateway status for a hosted checkout session (pair it with the sessionId/reference from an invoice payment to confirm the customer finished).

EdfaPayPlugin.searchTxnApi(
  parameters: {'search': 'ORD-111'},
  onSuccess: (result) => print('Matches: $result'),
  onError: (e) => print('Error: ${e['message']}'),
);

EdfaPayPlugin.pgTxnStatusApi(
  sessionId: 'pg-session-id',
  onSuccess: (result) => print('Gateway status: $result'),
  onError: (e) => print('Error: ${e['message']}'),
);

7: Online Receipts

Generate a shareable receipt URL for a transaction or a reconciliation, or open it directly. Use generateOnlineReceipt when you want the link (to render a QR, email it, etc.); use openOnlineReceipt to just launch it in the browser.

// Get the URL
EdfaPayPlugin.Extension.generateOnlineReceipt(
  type: ReceiptType.TRANSACTION, // or ReceiptType.RECONCILIATION
  id: 'transaction-or-reconciliation-id',
  onSuccess: (url) => print('Receipt: $url'),
  onError: (e) => print('Error: ${e['message']}'),
);

// Or open it straight away
EdfaPayPlugin.Extension.openOnlineReceipt(
  type: ReceiptType.TRANSACTION,
  id: 'transaction-id',
  onError: (e) => print('Error: ${e['message']}'),
);

8: Device Readiness (Permissions)

Reconciliation can run in the background and notify the merchant. On modern Android that needs several allowances that the OS won't grant silently. Check first, and request only when missing, so you don't nag users unnecessarily. Each request… returns whether it was granted.

Why it matters: without notifications the merchant misses reconciliation results; without exact-alarms / battery-unrestricted / (on aggressive OEMs) autostart, a scheduled reconciliation may never fire while the app is in Doze.

// Notifications (Android 13+)
if (!await EdfaPayPlugin.Utils.haveNotificationPermission()) {
  EdfaPayPlugin.Utils.requestNotificationPermission(
    callback: (granted) => print('Notifications: $granted'),
  );
}

// Exact alarms (Android 12+) — needed for on-time scheduled reconciliation
if (!await EdfaPayPlugin.Utils.canScheduleExactAlarms()) {
  EdfaPayPlugin.Utils.requestExactAlarmPermission(callback: (granted) => print('Exact alarms: $granted'));
}

// Battery "Unrestricted" — lets background work reach the network in Doze
if (!await EdfaPayPlugin.Utils.isBatteryUnrestricted()) {
  EdfaPayPlugin.Utils.requestBatteryUnrestricted(callback: (granted) => print('Battery: $granted'));
}

// Autostart allowlist — only some OEMs (no-op on stock Android)
if (await EdfaPayPlugin.Utils.deviceNeedsAutostart()) {
  EdfaPayPlugin.Utils.requestAutostartPermission(callback: (handled) => print('Autostart: $handled'));
}

9: Reconciliation Notification Sounds

Customise (or mute) the sounds played for reconciliation notifications. Pass a base64-encoded sound file per state; pass nothing to mute. This is a theme setting, so apply it before/around initialization like the rest of the theme.

import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;

Future<String> b64(String asset) async =>
    base64Encode((await rootBundle.load(asset)).buffer.asUint8List());

EdfaPayPlugin.theme().setReconciliationNotificationSounds(
  success: await b64('assets/sounds/success.wav'),
  error: await b64('assets/sounds/error.wav'),
  processing: await b64('assets/sounds/processing.wav'),
);

// Mute all reconciliation sounds
EdfaPayPlugin.theme().setReconciliationNotificationSounds();

10: API Key Credential & Unbind Terminal

Attach an API key to any credential with the chainable setApiKey, and detach the bound terminal from the device with Extension.unbindTerminal (e.g. before handing the device to another merchant).

final credentials = EdfaPayCredentials
    .withToken(environment: Env.PRODUCTION, token: 'login-token')
    .setApiKey('your-api-key');

EdfaPayPlugin.Extension.unbindTerminal(
  onSuccess: (res) => print('Unbound: $res'),
  onError: (e) => print('Error: ${e['message']}'),
);

Example

Instruction to Run Example:

// ignore_for_file: avoid_print
import 'dart:convert';

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/models/invoice_request.dart';
import 'package:edfapay_softpos_sdk/models/invoice_response.dart';
import 'package:edfapay_softpos_sdk/enums/env.dart';
import 'package:edfapay_softpos_sdk/enums/receipt_type.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.01";

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;
  String? _lastTxnId; // captured from the last purchase, used for the receipt

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

  @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))),
                  ElevatedButton(onPressed: checkout, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Checkout / BNPL", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: onlineReceipt, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Open Last Receipt", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: prepareDevice, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Prepare Device", style: TextStyle(color: Colors.black))),
                  ElevatedButton(onPressed: unbindTerminal, style: ButtonStyle(backgroundColor: MaterialStatePropertyAll(HexColor("06E59F"))), child: const Text("Unbind Terminal", 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: "user@mail.com",
        password: "Password@123"
    );

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

    EdfaPayCredentials credentials = EdfaPayCredentials.withEmail(
        environment: Env.DEVELOPMENT,
        email: "user@mail.com",
    );

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

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

    // Optionally attach an API key to any credential (chainable):
    // credentials = credentials.setApiKey("your-api-key");

    // setTheme();
    EdfaPayPlugin.initiate(
        credentials: credentials,
        onError: (e){
          toast("** Error Initializing SDK **");

          print('** Error Initializing SDK **');
          print('>>> Error');
          print('  >>> ${jsonEncode(e)}');

        },
        onTerminalBindingTask: (bindingTask){
          print('>>> Terminal Binding Required');

          List<Terminal> terminals = bindingTask.terminals;

          // Option 1: Show SDK's built-in terminal selection UI
          bindingTask.bind();

          // Option 2: Refresh terminal list, then show custom UI

          // Option 3: Bind programmatically or InApp UI list
          terminals.firstOrNull?.bind();
        },
        onSuccess: (sessionId){
          toast('** Successful Initialized SDK **');

          print('** Successful Initialized SDK **');
          print('>>> Success');
          print('  >>> Session ID: $sessionId');

          setState(() {
            _edfaPluginInitiated = true;
          });
        }
    );
  }

  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)
        .setShufflePinPad();
    EdfaPayPlugin.theme()
        .setPrimaryColor("#06E59F")
        .setSecondaryColor("#000000")
        .setHeaderImage(logo)
        .setPoweredByImage(logo)
        .setPresentation(presentation)
        // Custom reconciliation sounds: pass base64-encoded audio per state, e.g.
        //   .setReconciliationNotificationSounds(
        //       success: await assetsBase64('assets/sounds/success.wav'),
        //       error: await assetsBase64('assets/sounds/error.wav'),
        //       processing: await assetsBase64('assets/sounds/processing.wav'))
        // Called with no arguments it mutes every reconciliation sound:
        .setReconciliationNotificationSounds();
  }

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

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

    EdfaPayPlugin.enableLogs(true);
    EdfaPayPlugin.purchase(
        txnParams: params,
        flowType: FlowType.DETAIL,
        onPaymentProcessComplete: (status, code, result, isFlowCompleted) {
          toast("Transaction Completed | Check the logs");
          if(status){
            // Keep the transaction id so we can pull its online receipt later
            _lastTxnId = result?['transaction_number'] as String? ?? result?['id'] as String?;
            print(' >>> [ Success ]');
            print(' >>> [ ${jsonEncode(result)} ]');
          }else{
            print(' >>> [ Failed ]');
            print(' >>> [ ${jsonEncode(result)} ]');
          }
        },
        onRequestTimerEnd: () {
          toast("Server Request Timeout");

          print('>>> Server Timeout');
          print(' >>> The request timeout while performing transaction at backend');
        },
        onCardScanTimerEnd: () {
          toast("Scan Card 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: (error) {
          toast("Error occurred | Check the logs");
          print('>>> Error');
          print(' >>> "Scanning/Payment process through an error');
          print('  >>> ${jsonEncode(error)}');
        });
  }

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

    final params = TxnParams(
        amount: amountToPay,
        originalTransaction: Transaction.withRRN("1234567890", DateTime.now())
    );

    EdfaPayPlugin.enableLogs(true);
    EdfaPayPlugin.refund(txnParams: params, onPaymentProcessComplete: (status, code, result, isFlowCompleted) {
      toast("Card Payment Process Completed");
      print('>>> Payment Process Complete');
    }, onRequestTimerEnd: () {
      toast("Server Request Timeout");
      print('>>> Server Timeout');
      print(' >>> The request timeout while performing transaction at backend');
    }, onCardScanTimerEnd: () {
      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: (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()}');
        }
    );
  }

  // Hosted checkout (BNPL / payment-gateway link). Returns a deeplink you open
  // in a browser so the customer can complete payment outside the card UI.
  checkout() async {
    if (!_edfaPluginInitiated) {
      toast("Edfapay plugin not initialized.");
      return;
    }
    EdfaPayPlugin.invoicePayment(
      request: InvoicePaymentRequest(
        paymentMethod: PaymentMethod.tamara, // tamara | tabby | checkout
        merchantId: "your-merchant-id",
        amount: 11100, // minor units (111.00)
        currency: "SAR",
        phoneNumber: "+966512345678",
        email: "customer@example.com",
        orderReferenceId: "ORD-111",
        orderNumber: "10111",
        invoice: Invoice(
          total: 11100,
          lineItems: [
            InvoiceLineItem(sku: "SKU-001", description: "Product A", unitCost: 11100, quantity: 1, netTotal: 11100, total: 11100),
          ],
        ),
        additionalData: AdditionalData(storeId: "STORE-01"),
      ),
      onSuccess: (InvoicePaymentResponse res) {
        toast("Checkout created | Open the deeplink");
        print('>>> Deeplink: ${res.checkoutDeeplink}');
        print('>>> Txn: ${res.transactionId}');
      },
      onError: (e) {
        toast("Checkout error | Check the logs");
        print('  >>> ${jsonEncode(e)}');
      },
    );
  }

  // Build (and open) the online receipt for the last successful purchase.
  onlineReceipt() async {
    if (_lastTxnId == null) {
      toast("Make a purchase first");
      return;
    }
    EdfaPayPlugin.Extension.openOnlineReceipt(
      type: ReceiptType.TRANSACTION, // or ReceiptType.RECONCILIATION
      id: _lastTxnId!,
      onError: (e) => print('  >>> ${jsonEncode(e)}'),
    );
  }

  // Ask for the permissions background reconciliation relies on. Check first so
  // you only prompt when something is actually missing.
  prepareDevice() async {
    if (!await EdfaPayPlugin.Utils.haveNotificationPermission()) {
      EdfaPayPlugin.Utils.requestNotificationPermission(callback: (g) => print('Notifications: $g'));
    }
    if (!await EdfaPayPlugin.Utils.canScheduleExactAlarms()) {
      EdfaPayPlugin.Utils.requestExactAlarmPermission(callback: (g) => print('Exact alarms: $g'));
    }
    if (!await EdfaPayPlugin.Utils.isBatteryUnrestricted()) {
      EdfaPayPlugin.Utils.requestBatteryUnrestricted(callback: (g) => print('Battery: $g'));
    }
    if (await EdfaPayPlugin.Utils.deviceNeedsAutostart()) {
      EdfaPayPlugin.Utils.requestAutostartPermission(callback: (g) => print('Autostart: $g'));
    }
    toast("Requested device readiness permissions");
  }

  // Detach the bound terminal from this device (e.g. before reassigning it).
  unbindTerminal() async {
    EdfaPayPlugin.Extension.unbindTerminal(
      onSuccess: (res) {
        toast("Terminal unbound");
        setState(() => _edfaPluginInitiated = false);
      },
      onError: (e) {
        toast("Unbind error | Check the logs");
        print('  >>> ${jsonEncode(e)}');
      },
    );
  }

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

}

License

MIT