flutter_elavon 1.0.9
flutter_elavon: ^1.0.9 copied to clipboard
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),
),
);
},
),
),
],
),
),
),
);
}
}