Flutter Elavon Plugin

Flutter plugin for integrating Elavon Payment Gateway SDK (Converge Commerce SDK 6.8.0) into Flutter applications.

Features

  • Account creation and management
  • Device discovery and connection
  • Transaction processing (Sale, Refund, Pre-Auth, etc.)
  • Real-time transaction event streaming

Setup

Android

  1. Add the plugin and required dependencies to your pubspec.yaml:
dependencies:
  flutter_elavon: ^1.0.3  # or latest version from pub.dev
  path_provider: ^2.1.0  # Required by Elavon SDK

Important: The path_provider plugin is required because the Elavon SDK uses it internally. Make sure to add it explicitly to your app's dependencies.

  1. Add Elavon SDK Libraries: Due to size limitations, the Elavon SDK library files (JAR/AAR) are not included in the published package. You need to:

    Important: When using the plugin from pub.dev (published package):

    • The plugin's Java code needs Elavon SDK classes for compilation

    • You have two options:

      Option 1 (Easiest): Add files to the plugin's directory (one-time per Flutter installation):

      • Copy AAR/JAR files to: ~/.pub-cache/hosted/pub.dev/flutter_elavon-1.0.5/android/libs/
      • Replace 1.0.5 with your installed version
      • This is a one-time setup that persists across projects

      Option 2: Add files to your app's directory (requires Gradle property):

      • Add files to: your_project/android/app/libs/
      • REQUIRED: Add this to your app's android/gradle.properties file (create it if it doesn't exist):
        flutterElavonAppLibsPath=app/libs
        
        Or use an absolute path:
        flutterElavonAppLibsPath=/absolute/path/to/your/project/android/app/libs
        
      • Make sure your app's android/app/build.gradle has the repositories block (see Step 3)
      • Note: If Option 2 doesn't work, use Option 1 instead (more reliable and simpler)

    Step 1: Obtain the Elavon Commerce SDK 6.8.0 libraries from Elavon

    Step 2: Copy all JAR and AAR files to your app's android/app/libs/ directory

    • Create the directory if it doesn't exist: your_project/android/app/libs/
    • Copy all .jar and .aar files from the Elavon SDK to this directory
    • You do NOT need to add files to the plugin's directory when using the published package

    Step 3: Update your app's android/app/build.gradle:

    Add the repositories section (if not already present):

    repositories {
        flatDir {
            dirs 'libs'  // Points to android/app/libs/ directory
        }
    }
    

    Add the dependencies:

    dependencies {
        // Include all JAR files from libs directory
        implementation fileTree(dir: 'libs', include: ['*.jar'])
           
        // Include required AAR files
        implementation(name: 'commerce-android-6.8.0', ext: 'aar')
        implementation(name: 'commerce-common-android-6.8.0-debug', ext: 'aar')
        implementation(name: 'commerce-unifiedgateway-android-6.8.0-debug', ext: 'aar')
        implementation(name: 'deckard-android-6.8.0', ext: 'aar')
        implementation(name: 'mpos-release-1.0.10.39', ext: 'aar')
        implementation(name: 'opayo-core-android-6.8.0-debug', ext: 'aar')
        implementation(name: 'PclServiceLib-pcl-2.21.01', ext: 'aar')
        implementation(name: 'PclUtilities-pcl-2.21.01', ext: 'aar')
        implementation(name: 'rba-android-6.8.0-debug', ext: 'aar')
        implementation(name: 'roam-rua-wrapper-java-android-6.8.0-debug', ext: 'aar')
        implementation(name: 'roamreaderunifiedapi-2.5.2.1', ext: 'aar')
        implementation(name: 'rua-android-6.8.0', ext: 'aar')
        implementation(name: 'saf-android-aar-6.8.0-debug', ext: 'aar')
    }
    

    Complete Example - Here's how your android/app/build.gradle should look:

    android {
        // ... your android configuration ...
    }
       
    // Add repositories BEFORE dependencies
    repositories {
        flatDir {
            dirs 'libs'  // This points to android/app/libs/
        }
    }
       
    dependencies {
        // ... other dependencies ...
           
        // JAR files
        implementation fileTree(dir: 'libs', include: ['*.jar'])
           
        // AAR files
        implementation(name: 'commerce-android-6.8.0', ext: 'aar')
        implementation(name: 'deckard-android-6.8.0', ext: 'aar')
        // ... other AAR files ...
    }
    

    Important:

    • The repositories block with flatDir must be at the module level (inside android/app/build.gradle)
    • The dirs 'libs' path is relative to the android/app/ directory
    • Make sure all AAR files are in your_project/android/app/libs/ directory
    • If you put files in android/libs/ instead, use dirs '../libs'
  2. The plugin requires the following permissions (already included in AndroidManifest.xml):

    • Bluetooth permissions (BLUETOOTH_SCAN, BLUETOOTH_CONNECT for API 31+)
    • Location permission (ACCESS_FINE_LOCATION)
    • Phone state permission (READ_PHONE_STATE)
    • USB host feature (for USB device discovery)
  3. Minimum SDK: 28

  4. Target SDK: 35

Usage

⚠️ CRITICAL: Initialization Order

You MUST call WidgetsFlutterBinding.ensureInitialized() as the very first line in your main() function, before:

  • Any database initialization
  • Any file system access (path_provider)
  • Any plugin usage
  • Any other async operations
import 'package:flutter_elavon/flutter_elavon.dart';
import 'package:flutter/widgets.dart';

// ✅ CORRECT: ensureInitialized() is the first line
void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // MUST be first!
  
  // Now you can safely use plugins, databases, path_provider, etc.
  runApp(MyApp());
}

// ❌ WRONG: Don't do this!
void main() async {
  // This will cause path_provider channel errors!
  final db = await DBHelper.openDb(); // ❌ Called before ensureInitialized()
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

// Initialize and create account
final elavon = FlutterElavon();
await elavon.createAccount(PaymentGatewayType.CONVERGE);

// Find and connect devices
final devices = await elavon.findDevices();
if (devices.isNotEmpty) {
  await elavon.connectDevice(devices.first);
}

// Process a sale transaction
final result = await elavon.processSale(
  amount: 10000, // Amount in cents
  currencyCode: 'USD',
);

// Process a sale with transaction options (card types, entry types, etc.)
import 'package:flutter_elavon/models/transaction_options.dart';
import 'package:flutter_elavon/models/enums.dart';

final transactionOptions = TransactionOptions(
  // Card account types: CREDIT, DEBIT, EBT_FOOD_STAMP, EBT_CASH_BENEFIT
  allowedCardTypes: [CardType.CREDIT, CardType.DEBIT],
  
  // Or card brands: VISA, MASTERCARD, AMEX, DISCOVER, etc.
  // allowedCardTypes: [CardType.VISA, CardType.MASTERCARD],
  
  // Card entry types (optional)
  // allowedCardEntryTypes: ['SWIPE', 'EMV_CONTACT', 'EMV_PROXIMITY', 'MANUALLY_ENTERED'],
);

final result = await elavon.processSale(
  amount: 10000,
  currencyCode: 'USD',
  transactionOptions: transactionOptions,
);

// Listen to transaction events
elavon.transactionEvents.listen((event) {
  print('Transaction event: ${event.type}');
});

Troubleshooting

Build Error: Could not find AAR files

If you get errors like:

Could not find :commerce-android-6.8.0:.
Searched in the following locations:
  - file:/Users/.../android/libs/commerce-android-6.8.0.aar

Important: If you're using the plugin from pub.dev, you should ONLY need files in your app's directory. If you're being asked to add files to the plugin's directory, something is wrong.

Solution:

  1. Check file location: Make sure all AAR and JAR files are in the correct directory:

    • If using dirs 'libs' in repositories → files should be in android/app/libs/
    • If using dirs '../libs' → files should be in android/libs/
  2. Verify file names: Ensure the AAR file names match exactly (case-sensitive):

    • commerce-android-6.8.0.aar (not Commerce-Android-6.8.0.aar)
    • Check for exact matches including the .aar extension
  3. Check repositories block: Make sure the repositories block is in the correct location:

    // In android/app/build.gradle
    repositories {
        flatDir {
            dirs 'libs'  // Relative to android/app/
        }
    }
    
  4. Sync Gradle: After making changes:

    cd android
    ./gradlew clean
    cd ..
    flutter clean
    flutter pub get
    
  5. Verify files exist: Check that files are actually in the directory:

    ls -la android/app/libs/*.aar
    
  6. If you get "package com.elavon.commerce does not exist" error:

    • This means the plugin can't find the AAR files during compilation
    • Quick Fix: Use Option 1 - add files to plugin directory (one-time setup):
      # Find your plugin version
      flutter pub deps | grep flutter_elavon
           
      # Copy files to plugin directory (replace 1.0.5 with your version)
      cp -r your_project/android/app/libs/* ~/.pub-cache/hosted/pub.dev/flutter_elavon-1.0.5/android/libs/
      
    • Alternative: If using Option 2, make sure you've set the Gradle property:
      • Create/edit your_project/android/gradle.properties
      • Add: flutterElavonAppLibsPath=app/libs
      • Run flutter clean and rebuild
  7. If you're asked to add files to the plugin's directory: This shouldn't happen when using the published package. Try:

    • Run flutter clean and flutter pub get again
    • Check that you're using the plugin from pub.dev, not a local path:
      dependencies:
        flutter_elavon: ^1.0.4  # ✅ Correct - from pub.dev
        # flutter_elavon:
        #   path: ../flutter_elavon  # ❌ Wrong - this requires files in plugin directory
      
    • If using a local path dependency, you'll need files in both places (plugin's libs for compilation, app's libs for runtime)

PlatformException: Unable to establish connection on channel path_provider

If you encounter this error:

PlatformException(channel-error, Unable to establish connection on channel: "dev.flutter.pigeon.path_provider_android.PathProviderApi.getApplicationDocumentsPath"., null, null)

Root Cause: This error occurs when path_provider (or any plugin) is used before it's fully registered. Even if you call WidgetsFlutterBinding.ensureInitialized() first, there can be a race condition where plugin registration happens asynchronously. Common causes:

  • Calling getApplicationDocumentsDirectory() in main() before ensureInitialized()
  • Initializing databases in main() before ensureInitialized()
  • Using any Flutter plugins before ensureInitialized()
  • Race condition: Even with ensureInitialized() called first, plugins may not be fully registered yet when the SDK tries to access them

Solution:

  1. Move WidgetsFlutterBinding.ensureInitialized() to the very first line of your main() function:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized(); // MUST be first line!
         
      // Now safe to use plugins, databases, path_provider, etc.
      final db = await DBHelper.openDb(); // ✅ Now safe
      runApp(MyApp());
    }
    
  2. Ensure path_provider: ^2.1.0 is explicitly added to your app's pubspec.yaml dependencies

  3. Run a clean rebuild:

    flutter clean
    flutter pub get
    flutter run
    
  4. If you're still getting the error even after calling ensureInitialized() first, try adding a small delay:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
         
      // Small delay to ensure plugins are fully registered (handles race condition)
      await Future.delayed(Duration(milliseconds: 200));
         
      // Now safe to use plugins, databases, path_provider, etc.
      final db = await DBHelper.openDb();
      runApp(MyApp());
    }
    
  5. Check your code for any plugin usage before ensureInitialized():

    • Search for getApplicationDocumentsDirectory() calls
    • Search for database initialization
    • Search for any plugin method calls in main()
    • Move all of these after WidgetsFlutterBinding.ensureInitialized()

License

See LICENSE file for details.