flutter_elavon 1.0.9 copy "flutter_elavon: ^1.0.9" to clipboard
flutter_elavon: ^1.0.9 copied to clipboard

PlatformAndroid

Flutter plugin for Elavon Payment Gateway SDK

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_elavon/flutter_elavon.dart';
import 'package:flutter_elavon/models/gratuity_quick_value.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

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

class _MyAppState extends State<MyApp> {
  final _ingenico = FlutterElavon();
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
  AccountInfo? _accountInfo;
  List<Device> _devices = [];
  String _status = 'Ready';
  List<String> _logs = [];

  // Connection type selection
  ConnectionType _selectedConnectionType =
      ConnectionType.BT; // Default to Bluetooth

  // LED Pairing state
  String? _ledPairingDeviceName;
  List<String>? _ledPairingSequence;

  // Refund state variables
  String _selectedRefundType = 'STANDALONE_REFUND';
  final TextEditingController _refundAmountController =
      TextEditingController(text: '10.00');
  final TextEditingController _originalTransactionIdController =
      TextEditingController();
  final TextEditingController _cardNumberController = TextEditingController();
  final TextEditingController _cardExpirationDateController =
      TextEditingController();
  final TextEditingController _cardCvvController = TextEditingController();

  // Void state variables
  final TextEditingController _voidAmountController =
      TextEditingController(text: '0.00');
  final TextEditingController _voidTransactionIdController =
      TextEditingController();

  // Pre-Auth state variables
  final TextEditingController _preAuthAmountController =
      TextEditingController(text: '10.00');

  // Pre-Auth Complete state variables
  final TextEditingController _preAuthCompleteAmountController =
      TextEditingController(text: '10.00');
  final TextEditingController _preAuthCompleteTransactionIdController =
      TextEditingController();

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

  void _setupEventListener() {
    _ingenico.transactionEvents.listen((event) {
      setState(() {
        // Handle transaction progress events
        if (event.type == TransactionEventType.transactionProgress) {
          final progress = event.progress ?? '';
          _logs.add('progress: $progress');

          // Update status based on progress
          switch (progress) {
            case 'STARTING':
              _status = 'Starting transaction...';
              break;
            case 'CARD_READER_TRANSACTION_STARTED':
              _status = 'Card reader transaction started';
              break;
            case 'CARD_ENTRY_PROMPTED':
              _status = '👉 Please tap, insert, or swipe your card';
              _logs.add('👉 Please tap, insert, or swipe your card');
              break;
            case 'SMARTCARD_INSERTED':
              _status = 'Card inserted';
              break;
            case 'CARD_REMOVE_PROMPTED':
              _status = 'Please remove card';
              break;
            case 'SMARTCARD_REMOVED':
              _status = 'Card removed';
              break;
            case 'SMARTCARD_PROCESSING_ONLINE':
              _status = 'Processing online...';
              break;
            case 'SIGNATURE_VERIFICATION_STARTED':
              _status = 'Signature verification started';
              break;
            case 'CARD_READER_TRANSACTION_COMPLETED':
              _status = 'Card reader transaction completed';
              break;
            case 'TRANSACTION_COMPLETED':
              _status = 'Transaction completed';
              break;
            default:
              _status = 'Transaction in progress: $progress';
          }
        } else {
          _logs.add('Event: ${event.type}');
        }

        // Handle transaction completion with full details
        if (event.type == TransactionEventType.transactionDidComplete) {
          final outcome = event.data['outcome'];
          if (outcome != null) {
            _logs.add('');
            _logs.add('Transaction Response:');
            _logs.add('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');

            // Basic transaction info
            if (outcome['transactionType'] != null) {
              _logs.add('Transaction Type: ${outcome['transactionType']}');
            }
            if (outcome['authorizationResult'] != null) {
              _logs
                  .add('Transaction Result: ${outcome['authorizationResult']}');
            }
            if (outcome['identifier'] != null) {
              _logs.add('Identifier: ${outcome['identifier']}');
            }
            if (outcome['dateTime'] != null) {
              _logs.add('Date: ${outcome['dateTime']}');
            }
            if (outcome['clientTransactionIdentifier'] != null) {
              _logs.add(
                  'Client Txn ID: ${outcome['clientTransactionIdentifier']}');
            }

            // Amount information
            if (outcome['amountAuthorized'] != null) {
              final amount = (outcome['amountAuthorized'] as num) / 100;
              _logs.add('Amount Approved: \$${amount.toStringAsFixed(2)}');
            }

            // Card transaction details
            if (outcome['isCardTransaction'] == true) {
              _logs.add('');
              if (outcome['state'] != null) {
                _logs.add('Transaction State: ${outcome['state']}');
              }
              if (outcome['cardType'] != null) {
                _logs.add('Card Type: ${outcome['cardType']}');
              }
              if (outcome['cardScheme'] != null) {
                _logs.add('Card Scheme: ${outcome['cardScheme']}');
              }
              if (outcome['cardEntryType'] != null) {
                _logs.add('Entry Type: ${outcome['cardEntryType']}');
              }
              if (outcome['authorizationMode'] != null) {
                _logs.add('Auth Mode: ${outcome['authorizationMode']}');
              }
              if (outcome['maskPan'] != null) {
                _logs.add('Mask Pan: ${outcome['maskPan']}');
              }
              if (outcome['isFallback'] != null) {
                _logs.add('Fallback: ${outcome['isFallback']}');
              }
              if (outcome['authCode'] != null) {
                _logs.add('Auth Code: ${outcome['authCode']}');
              }
              if (outcome['issuerResponse'] != null) {
                _logs.add('Issuer Response: ${outcome['issuerResponse']}');
              }
              if (outcome['cardHoldersFirstName'] != null ||
                  outcome['cardHoldersLastName'] != null) {
                final firstName = outcome['cardHoldersFirstName'] ?? '';
                final lastName = outcome['cardHoldersLastName'] ?? '';
                _logs.add('CardHolder Name: ${firstName} ${lastName}'.trim());
              }
              if (outcome['originalAuthorizationResponseData'] != null) {
                _logs.add(
                    'OAR Data: ${outcome['originalAuthorizationResponseData']}');
              }
              if (outcome['ps2000Data'] != null) {
                _logs.add('PS2000 Data: ${outcome['ps2000Data']}');
              }
              if (outcome['accountBalance'] != null) {
                final balance = (outcome['accountBalance'] as num) / 100;
                _logs.add('Account Balance: \$${balance.toStringAsFixed(2)}');
              }
              if (outcome['cardExpirationDate'] != null) {
                _logs.add('Exp Date: ${outcome['cardExpirationDate']}');
              }
              if (outcome['transactionLanguage'] != null) {
                _logs.add(
                    'Cardholder language: ${outcome['transactionLanguage']}');
              }
              if (outcome['surchargeStatus'] != null) {
                _logs.add('Surcharge Status: ${outcome['surchargeStatus']}');
              }

              // EMV-specific details
              if (outcome['iccAppName'] != null) {
                _logs.add('');
                _logs.add('EMV Details:');
                _logs.add('App Name: ${outcome['iccAppName']}');
                if (outcome['iccAid'] != null) {
                  _logs.add('AID: ${outcome['iccAid']}');
                }
                if (outcome['issuerApplicationData'] != null) {
                  _logs.add('IAD: ${outcome['issuerApplicationData']}');
                }
                if (outcome['iccCsn'] != null) {
                  _logs.add('Csn: ${outcome['iccCsn']}');
                }
                if (outcome['iccCvmr'] != null) {
                  _logs.add('Cvmr: ${outcome['iccCvmr']}');
                }
                if (outcome['iccTsi'] != null) {
                  _logs.add('Tsi: ${outcome['iccTsi']}');
                }
                if (outcome['iccTvr'] != null) {
                  _logs.add('Tvr: ${outcome['iccTvr']}');
                }
                if (outcome['iccAc'] != null) {
                  _logs.add('Ac: ${outcome['iccAc']}');
                }
                if (outcome['iccArc'] != null) {
                  _logs.add('Arc: ${outcome['iccArc']}');
                }
                if (outcome['iccAtc'] != null) {
                  _logs.add('Atc: ${outcome['iccAtc']}');
                }
              }
            }

            _logs.add('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
            _logs.add('');

            // Update status
            if (outcome['approved'] == true) {
              _status = 'Transaction approved';
            } else {
              _status = 'Transaction declined';
            }
          }
        }

        // Handle transaction failure
        if (event.type == TransactionEventType.transactionDidFail) {
          if (event.errors != null && event.errors!.isNotEmpty) {
            _logs.add('Transaction Failed:');
            for (var error in event.errors!) {
              // Use formattedMessage if available (includes nested errors), otherwise use debugDescription
              if (error.formattedMessage.isNotEmpty) {
                _logs.add(error.formattedMessage);
              } else {
                _logs.add('  Error: ${error.debugDescription}');
              }
            }
          }
          _status = 'Transaction failed';
        }

        // Handle signature requirement
        if (event.type == TransactionEventType.shouldProvideInformation) {
          final requirements = event.requirements;
          if (requirements != null &&
              requirements['requiresSignatureVerification'] == 'REQUIRED') {
            _logs.add('Tender requires:');
            _logs.add('  Signature');
            _logs.add('Providing signature');
          }
        }

        // Handle LED pairing sequence
        // Note: Native Android dialog is shown directly from Android side
        // This event is just for logging purposes
        if (event.type == TransactionEventType.deviceLedPairingSequence) {
          final deviceName =
              event.data['deviceName'] as String? ?? 'Unknown Device';
          final message = event.data['message'] as String? ??
              'LED pairing sequence required';
          final ledSequence = event.data['ledSequence'] as List<dynamic>?;

          _log('LED Pairing: $message');
          _log('Device: $deviceName');
          if (ledSequence != null && ledSequence.isNotEmpty) {
            _log('LED Sequence: ${ledSequence.join(", ")}');
          }
          _log('Native Android dialog should be displayed');
        }
      });
    });
  }

  void _log(String message) {
    setState(() {
      _logs.add('${DateTime.now().toString().substring(11, 19)}: $message');
    });
  }

  Future<void> _createAccount() async {
    try {
      setState(() {
        _status = 'Creating account...';
      });
      _log('Creating account with CONVERGE payment gateway');
      final credentials = ConvergeCredentials(
        merchantId: '0030727',
        userId: 'apiuser103305',
        pin: 'DBYFLZ5ZSMM5QAC80LSD60ZL26LJXAE79TZCUJVB03ZAMTDYXXAW4JKGHKYJ9K62',
        //pin: 'V0FQ1GASRN874L6YUT45ABVQYFW6Y27TZITG15RHCZAB0L3W187ZX6YTFBX0IIMR',
        partnerAppId: '',
        vendorId: 'sc900499',
        vendorAppName: 'Commerce sample App',
        vendorAppVersion: '6.8.0.33',
        bmsUsername: '',
        bmsPassword: '',
        serverType: 'DEMO', // or 'PROD', etc.
      );

      _accountInfo = await _ingenico.createAccount(
        PaymentGatewayType.CONVERGE,
        credentials: credentials,
      );

      setState(() {
        _status = 'Account created successfully';
      });
      _log('Account created: ${_accountInfo!.name}');
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error creating account: $e');
    }
  }

  Future<void> _findDevices() async {
    try {
      setState(() {
        _status = 'Searching for devices...';
      });
      _log(
          'Searching for devices with connection type: ${_selectedConnectionType.name}');
      // Pass connection type to filter devices
      _devices =
          await _ingenico.findDevices(connectionType: _selectedConnectionType);
      setState(() {
        _status = 'Found ${_devices.length} device(s)';
      });
      _log('Found ${_devices.length} device(s)');
      for (var device in _devices) {
        _log(
            '  - ${device.name} (${device.deviceTypes.join(", ")}) [${device.connectionTypes.join(", ")}]');
      }
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error finding devices: $e');
    }
  }

  Future<void> _connectDevice() async {
    if (_devices.isEmpty) {
      _log('No devices available. Please search for devices first.');
      return;
    }

    try {
      setState(() {
        _status = 'Connecting to device...';
      });
      _log('Connecting to ${_devices.first.name}');
      _log('Using connection type: ${_selectedConnectionType.name}');
      await _ingenico.connectDevice(_devices.first, _selectedConnectionType);
      setState(() {
        _status = 'Device connected';
      });
      _log('Device connected successfully');
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error connecting device: $e');
    }
  }

  Future<void> _cancelTransaction() async {
    try {
      setState(() {
        _status = 'Cancelling transaction...';
      });
      _log('Cancelling current transaction...');

      await _ingenico.cancelTransaction();

      setState(() {
        _status = 'Transaction cancelled';
      });
      _log('Transaction cancelled successfully');
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error cancelling transaction: $e');
    }
  }

  void _showLedPairingDialog() {
    if (!mounted) return;

    // Use navigator key context if available, otherwise use state context
    final navigatorContext = _navigatorKey.currentContext ?? context;

    showDialog(
      context: navigatorContext,
      barrierDismissible: false,
      builder: (BuildContext dialogContext) {
        return AlertDialog(
          title: const Text('LED Pairing Confirmation'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Device: ${_ledPairingDeviceName ?? "Unknown"}'),
              const SizedBox(height: 16),
              const Text(
                'Please confirm the LED sequence on the device matches the pattern shown.',
                style: TextStyle(fontSize: 14),
              ),
              if (_ledPairingSequence != null &&
                  _ledPairingSequence!.isNotEmpty) ...[
                const SizedBox(height: 12),
                const Text(
                  'LED Sequence:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                ..._ledPairingSequence!.map((seq) => Padding(
                      padding: const EdgeInsets.symmetric(vertical: 2.0),
                      child: Text('• $seq'),
                    )),
              ],
            ],
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(dialogContext).pop();
                _cancelLedPairing();
              },
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(dialogContext).pop();
                _restartLedPairing();
              },
              child: const Text('Restart'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(dialogContext).pop();
                _confirmLedPairing();
              },
              child: const Text('Confirm'),
            ),
          ],
        );
      },
    );
  }

  Future<void> _confirmLedPairing() async {
    try {
      await _ingenico.confirmLedPairing();
      setState(() {
        _ledPairingDeviceName = null;
        _ledPairingSequence = null;
      });
      _log('LED pairing confirmed');
    } catch (e) {
      _log('Error confirming LED pairing: $e');
    }
  }

  Future<void> _cancelLedPairing() async {
    try {
      await _ingenico.cancelLedPairing();
      setState(() {
        _ledPairingDeviceName = null;
        _ledPairingSequence = null;
      });
      _log('LED pairing cancelled');
    } catch (e) {
      _log('Error cancelling LED pairing: $e');
    }
  }

  Future<void> _restartLedPairing() async {
    try {
      await _ingenico.restartLedPairing();
      _log('LED pairing sequence restarted');
      // Don't close dialog as restart will trigger sequence again
    } catch (e) {
      _log('Error restarting LED pairing: $e');
    }
  }

  Future<void> _processSale() async {
    try {
      setState(() {
        _status = 'Processing sale transaction...';
      });
      _log('Processing sale transaction: \$10.00');

      // Example: Create transaction options with custom parameters
      final transactionOptions = TransactionOptions(
        // Card presence and interaction
        //cardPresence: 'YES', // or 'NO', or null for default
        //shopperInteractionType: 'TELEPHONE_ORDER', // or 'MERCHANT_INITIATED', or null
        partialApprovalAllowed: false, // or true, or null for default

        // Card Entry Types (optional - defaults to all types if not specified)
        // allowedCardEntryTypes: ['SWIPE', 'EMV_CONTACT', 'EMV_PROXIMITY', 'MANUALLY_ENTERED'],

        // Card Types (optional - allow all if not specified)
        // Card brand types: ['VISA', 'MASTERCARD', 'AMEX', 'DISCOVER', etc.]
        // Card account types: ['CREDIT', 'DEBIT', 'EBT_FOOD_STAMP', 'EBT_CASH_BENEFIT']
        // Examples:
        // allowedCardTypes: ['VISA', 'MASTERCARD'],  // Allow specific card brands
        // allowedCardTypes: ['CREDIT', 'DEBIT'],      // Allow credit and debit cards
        // allowedCardTypes: ['EBT_FOOD_STAMP'], // Allow only EBT food stamp cards

        // Token options (optional)
        // generateToken: true,
        // addToken: true,
        // credentialOnFileType: 'UNSCHEDULED',

        // AVS fields (optional)
        // avsFirstName: 'John',
        // avsLastName: 'Doe',
        // avsAddress: '123 Main St',
        // avsZip: '12345',

        // Other options (optional)
        // requiresVoiceReferral: false,
        bypassDuplicateTransactionCheck: false,
        // signatureOption: 'SIGNATURE_REQUIRED',

        // Gratuity (optional)
        // gratuityAmount: 200, // $2.00 in cents
        gratuityRequested: true,
        gratuityCustomAmountEntryAllowed: true,
        // Gratuity quick values - customers can quickly select from these options
        gratuityQuickValues: [
          GratuityQuickValue.amount(200), // 15%
          GratuityQuickValue.amount(500), // 18%
          GratuityQuickValue.amount(700), // 20%
        ],
      );

      // Log start of transaction
      setState(() {
        _logs.add('Start Transaction: SALE/CARD');
        _status = 'Starting transaction...';
      });

      // Process sale - results will be shown via event listener
      await _ingenico.processSale(
        amount: 100, // $0.10 in cents
        currencyCode: 'USD',
        transactionOptions: transactionOptions, // Pass transaction options
      );

      // Note: The transaction result details are now shown in the event listener
      // when transactionDidComplete event is received
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error processing sale: $e');
    }
  }

  Future<void> _processPreAuth() async {
    try {
      // Parse pre-auth amount
      final amountText = _preAuthAmountController.text.trim();
      if (amountText.isEmpty) {
        _log('Error: Please enter a pre-authorization amount');
        return;
      }

      final amount =
          (double.parse(amountText) * 100).toInt(); // Convert to cents

      setState(() {
        _status = 'Processing pre-authorization transaction...';
      });
      _log('Processing pre-authorization transaction: \$${amountText}');

      // Example: Create transaction options with custom parameters
      final transactionOptions = TransactionOptions(
        partialApprovalAllowed: false, // or true, or null for default

        // Card Entry Types (optional - defaults to all types if not specified)
        // allowedCardEntryTypes: ['SWIPE', 'EMV_CONTACT', 'EMV_PROXIMITY', 'MANUALLY_ENTERED'],

        // Card Types (optional - allow all if not specified)
        // Card brand types: ['VISA', 'MASTERCARD', 'AMEX', 'DISCOVER', etc.]
        // Card account types: ['CREDIT', 'DEBIT', 'EBT_FOOD_STAMP', 'EBT_CASH_BENEFIT']
        // Examples:
        // allowedCardTypes: ['VISA', 'MASTERCARD'],  // Allow specific card brands
        // allowedCardTypes: ['CREDIT', 'DEBIT'],      // Allow credit and debit cards
        allowedCardTypes: ['EBT_FOOD_STAMP'], // Allow only EBT food stamp cards

        // Token options (optional)
        // generateToken: true,
        // addToken: true,
        // credentialOnFileType: 'UNSCHEDULED',

        // AVS fields (optional)
        // avsFirstName: 'John',
        // avsLastName: 'Doe',
        // avsAddress: '123 Main St',
        // avsZip: '12345',

        // Other options (optional)
        // requiresVoiceReferral: false,
        // bypassDuplicateTransactionCheck: false,
        // signatureOption: 'SIGNATURE_REQUIRED',
      );

      // Log start of transaction
      setState(() {
        _logs.add('Start Transaction: PRE_AUTH/CARD');
        _status = 'Starting pre-authorization transaction...';
      });

      // Process pre-auth - results will be shown via event listener
      await _ingenico.processPreAuth(
        amount: amount,
        currencyCode: 'USD',
        transactionOptions: transactionOptions,
      );

      // Note: The transaction result details are now shown in the event listener
      // when transactionDidComplete event is received
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error processing pre-auth: $e');
    }
  }

  Future<void> _processRefund() async {
    try {
      // Parse refund amount
      final amountText = _refundAmountController.text.trim();
      if (amountText.isEmpty) {
        _log('Error: Please enter a refund amount');
        return;
      }

      final amount =
          (double.parse(amountText) * 100).toInt(); // Convert to cents
      final refundType = _selectedRefundType;
      final originalTransactionId =
          _originalTransactionIdController.text.trim();

      // Validate linked refund
      if (refundType == 'LINKED_REFUND' && originalTransactionId.isEmpty) {
        _log('Error: Original Transaction ID is required for LINKED_REFUND');
        return;
      }

      setState(() {
        _status =
            'Processing ${refundType == 'LINKED_REFUND' ? 'linked' : 'standalone'} refund transaction...';
      });

      _log(
          'Processing ${refundType == 'LINKED_REFUND' ? 'LINKED' : 'STANDALONE'} refund: \$${amountText}');
      if (refundType == 'LINKED_REFUND') {
        _log('Original Transaction ID: $originalTransactionId');
      }

      // Log start of transaction
      setState(() {
        _logs.add('Start Transaction: ${refundType}/CARD');
        _status = 'Starting refund transaction...';
      });

      // Create transaction options with card details for STANDALONE_REFUND
      TransactionOptions? transactionOptions;
      if (refundType == 'STANDALONE_REFUND') {
        final cardNumber = _cardNumberController.text.trim();
        final cardExpirationDate = _cardExpirationDateController.text.trim();
        final cardCvv = _cardCvvController.text.trim();

        // Only set options if card number is provided (manual entry)
        if (cardNumber.isNotEmpty) {
          transactionOptions = TransactionOptions(
            cardNumber: cardNumber,
            cardExpirationDate:
                cardExpirationDate.isNotEmpty ? cardExpirationDate : null,
            cardCvv: cardCvv.isNotEmpty ? cardCvv : null,
          );
        }
      }

      // Process refund - results will be shown via event listener
      await _ingenico.processRefund(
        amount: amount,
        currencyCode: 'USD',
        originalTransactionId:
            refundType == 'LINKED_REFUND' ? originalTransactionId : null,
        transactionOptions: transactionOptions,
      );

      // Note: The transaction result details are now shown in the event listener
      // when transactionDidComplete event is received
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error processing refund: $e');
    }
  }

  Future<void> _processVoid() async {
    try {
      // Parse void amount (can be 0, but currency code is required)
      final amountText = _voidAmountController.text.trim();
      if (amountText.isEmpty) {
        _log('Error: Please enter an amount (can be 0.00)');
        return;
      }

      final amount =
          (double.parse(amountText) * 100).toInt(); // Convert to cents
      final originalTransactionId = _voidTransactionIdController.text.trim();

      // Validate transaction ID
      if (originalTransactionId.isEmpty) {
        _log('Error: Original Transaction ID is required for VOID');
        return;
      }

      setState(() {
        _status = 'Processing void transaction...';
      });

      _log('Processing VOID: \$${amountText}');
      _log('Original Transaction ID: $originalTransactionId');

      // Log start of transaction
      setState(() {
        _logs.add('Start Transaction: VOID/CARD');
        _status = 'Starting void transaction...';
      });

      // Process void - results will be shown via event listener
      await _ingenico.processVoid(
        amount: amount,
        currencyCode: 'USD',
        originalTransactionId: originalTransactionId,
      );

      // Note: The transaction result details are now shown in the event listener
      // when transactionDidComplete event is received
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error processing void: $e');
    }
  }

  Future<void> _processPreAuthComplete() async {
    try {
      // Parse pre-auth complete amount
      final amountText = _preAuthCompleteAmountController.text.trim();
      if (amountText.isEmpty) {
        _log('Error: Please enter a pre-auth complete amount');
        return;
      }

      final amount =
          (double.parse(amountText) * 100).toInt(); // Convert to cents
      final originalTransactionId =
          _preAuthCompleteTransactionIdController.text.trim();

      // Validate transaction ID
      if (originalTransactionId.isEmpty) {
        _log(
            'Error: Original Transaction ID is required for PRE_AUTH_COMPLETE');
        return;
      }

      setState(() {
        _status = 'Processing pre-auth complete transaction...';
      });

      _log('Processing PRE_AUTH_COMPLETE: \$${amountText}');
      _log('Original Transaction ID: $originalTransactionId');

      // Log start of transaction
      setState(() {
        _logs.add('Start Transaction: PRE_AUTH_COMPLETE/CARD');
        _status = 'Starting pre-auth complete transaction...';
      });

      // Process pre-auth complete - results will be shown via event listener
      await _ingenico.processPreAuthComplete(
        amount: amount,
        currencyCode: 'USD',
        originalTransactionId: originalTransactionId,
      );

      // Note: The transaction result details are now shown in the event listener
      // when transactionDidComplete event is received
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
      _log('Error processing pre-auth complete: $e');
    }
  }

  @override
  void dispose() {
    _refundAmountController.dispose();
    _originalTransactionIdController.dispose();
    _cardNumberController.dispose();
    _cardExpirationDateController.dispose();
    _cardCvvController.dispose();
    _voidAmountController.dispose();
    _voidTransactionIdController.dispose();
    _preAuthAmountController.dispose();
    _preAuthCompleteAmountController.dispose();
    _preAuthCompleteTransactionIdController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _navigatorKey,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Ingenico Mobile Solutions Example'),
        ),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Status: $_status',
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      if (_accountInfo != null) ...[
                        const SizedBox(height: 8),
                        Text('Account: ${_accountInfo!.name}'),
                        Text('Currency: ${_accountInfo!.currencyCode}'),
                      ],
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Connection Settings',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Connection Type:'),
                      const SizedBox(height: 4),
                      DropdownButtonFormField<ConnectionType>(
                        value: _selectedConnectionType,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                        items: ConnectionType.values.map((ConnectionType type) {
                          return DropdownMenuItem<ConnectionType>(
                            value: type,
                            child: Text(type.name),
                          );
                        }).toList(),
                        onChanged: (ConnectionType? value) {
                          if (value != null) {
                            setState(() {
                              _selectedConnectionType = value;
                            });
                          }
                        },
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Wrap(
                spacing: 8,
                runSpacing: 8,
                children: [
                  ElevatedButton(
                    onPressed: _createAccount,
                    child: const Text('Create Account'),
                  ),
                  ElevatedButton(
                    onPressed: _findDevices,
                    child: const Text('Find Devices'),
                  ),
                  ElevatedButton(
                    onPressed: _connectDevice,
                    child: const Text('Connect Device'),
                  ),
                  ElevatedButton(
                    onPressed: _processSale,
                    child: const Text('Process Sale'),
                  ),
                  ElevatedButton(
                    onPressed: _cancelTransaction,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.red,
                      foregroundColor: Colors.white,
                    ),
                    child: const Text('Cancel Transaction'),
                  ),
                ],
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Pre-Authorization Transaction',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Amount (\$):'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _preAuthAmountController,
                        keyboardType: const TextInputType.numberWithOptions(
                            decimal: true),
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: '10.00',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      const SizedBox(height: 12),
                      ElevatedButton(
                        onPressed: _processPreAuth,
                        style: ElevatedButton.styleFrom(
                          minimumSize: const Size(double.infinity, 40),
                        ),
                        child: const Text('Process Pre-Auth'),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Refund Transaction',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Refund Type:'),
                      const SizedBox(height: 4),
                      DropdownButtonFormField<String>(
                        value: _selectedRefundType,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                        items: const [
                          DropdownMenuItem(
                            value: 'STANDALONE_REFUND',
                            child: Text('STANDALONE_REFUND'),
                          ),
                          DropdownMenuItem(
                            value: 'LINKED_REFUND',
                            child: Text('LINKED_REFUND'),
                          ),
                        ],
                        onChanged: (value) {
                          setState(() {
                            _selectedRefundType = value!;
                          });
                        },
                      ),
                      const SizedBox(height: 12),
                      const Text('Refund Amount (\$):'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _refundAmountController,
                        keyboardType: const TextInputType.numberWithOptions(
                            decimal: true),
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: '10.00',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      if (_selectedRefundType == 'LINKED_REFUND') ...[
                        const SizedBox(height: 12),
                        const Text('Original Transaction ID:'),
                        const SizedBox(height: 4),
                        TextField(
                          controller: _originalTransactionIdController,
                          decoration: const InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'Enter original transaction ID',
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: 12,
                              vertical: 8,
                            ),
                          ),
                        ),
                      ],
                      if (_selectedRefundType == 'STANDALONE_REFUND') ...[
                        const SizedBox(height: 12),
                        const Text(
                          'Card Details (for manual entry):',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 8),
                        const Text('Card Number:'),
                        const SizedBox(height: 4),
                        TextField(
                          controller: _cardNumberController,
                          keyboardType: TextInputType.number,
                          decoration: const InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'Enter card number',
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: 12,
                              vertical: 8,
                            ),
                          ),
                        ),
                        const SizedBox(height: 12),
                        const Text('Expiration Date (MMYY):'),
                        const SizedBox(height: 4),
                        TextField(
                          controller: _cardExpirationDateController,
                          keyboardType: TextInputType.number,
                          maxLength: 4,
                          decoration: const InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'MMYY',
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: 12,
                              vertical: 8,
                            ),
                          ),
                        ),
                        const SizedBox(height: 12),
                        const Text('CVV:'),
                        const SizedBox(height: 4),
                        TextField(
                          controller: _cardCvvController,
                          keyboardType: TextInputType.number,
                          maxLength: 4,
                          obscureText: true,
                          decoration: const InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'CVV',
                            contentPadding: EdgeInsets.symmetric(
                              horizontal: 12,
                              vertical: 8,
                            ),
                          ),
                        ),
                        const SizedBox(height: 8),
                        const Text(
                          'Note: If card number is not provided, the card reader will be used.',
                          style: TextStyle(
                            fontSize: 12,
                            fontStyle: FontStyle.italic,
                            color: Colors.grey,
                          ),
                        ),
                      ],
                      const SizedBox(height: 12),
                      ElevatedButton(
                        onPressed: _processRefund,
                        style: ElevatedButton.styleFrom(
                          minimumSize: const Size(double.infinity, 40),
                        ),
                        child: const Text('Process Refund'),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Void Transaction',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Amount (\$) - can be 0.00:'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _voidAmountController,
                        keyboardType: const TextInputType.numberWithOptions(
                            decimal: true),
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: '0.00',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Original Transaction ID:'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _voidTransactionIdController,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: 'Enter transaction ID to void',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      const SizedBox(height: 12),
                      ElevatedButton(
                        onPressed: _processVoid,
                        style: ElevatedButton.styleFrom(
                          minimumSize: const Size(double.infinity, 40),
                        ),
                        child: const Text('Process Void'),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Pre-Auth Complete Transaction',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Amount (\$):'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _preAuthCompleteAmountController,
                        keyboardType: const TextInputType.numberWithOptions(
                            decimal: true),
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: '10.00',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      const SizedBox(height: 12),
                      const Text('Original Pre-Auth Transaction ID:'),
                      const SizedBox(height: 4),
                      TextField(
                        controller: _preAuthCompleteTransactionIdController,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: 'Enter original PRE_AUTH transaction ID',
                          contentPadding: EdgeInsets.symmetric(
                            horizontal: 12,
                            vertical: 8,
                          ),
                        ),
                      ),
                      const SizedBox(height: 12),
                      ElevatedButton(
                        onPressed: _processPreAuthComplete,
                        style: ElevatedButton.styleFrom(
                          minimumSize: const Size(double.infinity, 40),
                        ),
                        child: const Text('Process Pre-Auth Complete'),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              const Text(
                'Logs:',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Container(
                height: 300,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                  borderRadius: BorderRadius.circular(4),
                ),
                child: ListView.builder(
                  itemCount: _logs.length,
                  itemBuilder: (context, index) {
                    return Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8.0,
                        vertical: 4.0,
                      ),
                      child: Text(
                        _logs[index],
                        style: const TextStyle(fontSize: 12),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}