mx_merchant 0.0.1-beta.6 copy "mx_merchant: ^0.0.1-beta.6" to clipboard
mx_merchant: ^0.0.1-beta.6 copied to clipboard

A Flutter/Dart client package for integrating with the MX Merchant REST APIs, including checkout, payments, customers, vaulted accounts, reports, notifications, merchant accounts, and sandbox/producti [...]

example/lib/main.dart

import 'dart:convert';
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mx_merchant/mx_merchant.dart';

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

class MXMerchantExampleApp extends StatelessWidget {
  const MXMerchantExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MX Merchant Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6366F1), brightness: Brightness.light),
        useMaterial3: true,
        cardTheme: Theme.of(context).cardTheme.copyWith(elevation: 2, shape: RoundedRectangleBorder(borderRadius: .circular(12))),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            padding: const .symmetric(horizontal: 24, vertical: 12),
            shape: RoundedRectangleBorder(borderRadius: .circular(8)),
          ),
        ),
        inputDecorationTheme: InputDecorationTheme(
          border: OutlineInputBorder(borderRadius: .circular(8)),
          contentPadding: const .symmetric(horizontal: 16, vertical: 12),
        ),
      ),
      home: const MXMerchantHomePage(),
    );
  }
}

class MXMerchantHomePage extends StatefulWidget {
  const MXMerchantHomePage({super.key});

  @override
  State<MXMerchantHomePage> createState() => _MXMerchantHomePageState();
}

class _MXMerchantHomePageState extends State<MXMerchantHomePage> with TickerProviderStateMixin {
  late final TabController _tabController;

  final _consumerKeyController = TextEditingController(text: '');
  final _consumerSecretController = TextEditingController(text: '');
  final _merchantIdController = TextEditingController(text: '');

  bool _isLoading = false;
  String _lastResult = '';
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 5, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    _consumerKeyController.dispose();
    _consumerSecretController.dispose();
    _merchantIdController.dispose();
    super.dispose();
  }

  MxMerchant _createMerchant() {
    return MxMerchant(
      consumerKey: _consumerKeyController.text.trim(),
      consumerSecret: _consumerSecretController.text.trim(),
      merchantId: _merchantIdController.text.trim(),
      env: .sandbox,
    );
  }

  void _setLoading(bool loading) {
    setState(() {
      _isLoading = loading;
      _errorMessage = null;
    });
  }

  void _setResult(String result) {
    setState(() {
      _lastResult = result;
    });
  }

  void _setError(String error) {
    setState(() {
      _errorMessage = error;
      _isLoading = false;
    });
  }

  Future<void> _testPaymentFlow() async {
    if (!_validateCredentials()) return;

    _setLoading(true);
    try {
      final merchant = _createMerchant();
      final results = <String, dynamic>{};

      // Make Payment
      final paymentResult = await merchant.payment.makePayment(
        MxPaymentRequestModel(
          amount: 13.00,
          tenderType: .card,
          paymentType: .sale,
          cardAccount: MxCardModel(number: '4242424242424242', expiryMonth: '12', expiryYear: (DateTime.now().year + 1).toString(), cvv: '123'),
          customerName: 'Test Customer',
          customerCode: 'TEST001',
          replayId: 13,
          source: .api,
        ),
      );
      results['payment'] = paymentResult.toJson();
      log("payment = ${results['payment']}");

      // Get Payments
      final getPaymentsResult = await merchant.payment.getPayments(MxGetPaymentRequestModel(limit: 2));
      results['getPayments'] = getPaymentsResult.toJson();
      log("getPayments = ${results['getPayments']}");

      // Get Specific Payment
      final getAPaymentResult = await merchant.payment.getAPayment(paymentResult.id.toString(), includeCustomer: true);
      results['getAPayment'] = getAPaymentResult.toJson();
      log("getAPayment = ${results['getAPayment']}");

      // Void Payment
      final voidResult = await merchant.payment.voidAPayment(paymentResult.id.toString(), force: true);
      results['voidPayment'] = voidResult;
      log("voidPayment = ${results['voidPayment']}");

      // Send Receipt
      final receiptResult = await merchant.payment.sendAPaymentReceipt(paymentId: paymentResult.id.toString(), contact: 'huypanha558@gmail.com');
      results['sendReceipt'] = receiptResult;
      log("sendReceipt = ${results['sendReceipt']}");

      log(jsonEncode(results));
      _setResult(jsonEncode(results));
    } catch (e, s) {
      log("Payment flow failed: $e", stackTrace: s);
      _setError('Payment flow failed: $e');
    } finally {
      _setLoading(false);
    }
  }

  Future<void> _testTerminalFlow() async {
    if (!_validateCredentials()) return;

    _setLoading(true);
    try {
      final merchant = _createMerchant();
      final results = <String, dynamic>{};

      // Create Terminal
      final createResult = await merchant.terminal.create(
        MxTerminalRequestModel(
          providerKey: 'anywherecommerce',
          enabled: true,
          name: 'Test Terminal',
          description: 'Test terminal for demonstration',
          defaultModel: 'Sherpa',
          tip: MxTerminalTipModel(options: [MxTerminalTipOptionModel(rate: MxTerminalTipRateModel())]),
          entryModes: MxTerminalEntryModeModel(),
          paymentMethods: MxTerminalPaymentMethodModel(),
        ),
      );
      results['createTerminal'] = createResult.toJson();

      // Get Terminals
      final terminalsResult = await merchant.terminal.getListOfTerminals();
      results['getTerminals'] = terminalsResult.map((e) => e.toJson()).toList();

      // Delete Terminals
      final deleteTerminalResult = await merchant.terminal.delete(terminalsResult.first.id.toString());
      results['deleteTerminalResult'] = deleteTerminalResult;

      log(jsonEncode(results));
      _setResult(jsonEncode(results));
    } catch (e, s) {
      log("Terminal flow failed: $e", stackTrace: s);
      _setError('Terminal flow failed: $e');
    } finally {
      _setLoading(false);
    }
  }

  Future<void> _testCustomersFlow() async {
    if (!_validateCredentials()) return;

    _setLoading(true);
    try {
      final merchant = _createMerchant();
      final results = <String, dynamic>{};

      // Create Customer
      final createResult = await merchant.customer.create(
        MxCreateCustomerRequestModel(
          firstName: 'Huy',
          lastName: 'Panha',
          email: 'email@example.com',
          phone: '+1234567890',
          name: 'Huy Panha',
          activeStatus: true,
        ),
      );
      results['createCustomer'] = createResult.toJson();

      // Get Customers
      final getResults = await merchant.customer.get(MxGetCustomerRequestModel(limit: 10));
      results['getCustomers'] = getResults.records?.map((e) => e.toJson()).toList();

      // Get Specific Customer
      final getCustomerResult = await merchant.customer.getACustomer(createResult.id);
      results['getCustomer'] = getCustomerResult.toJson();

      // Update Customer
      final updateCustomerResult = await merchant.customer.update(
        customerData: createResult.copyWith(name: 'Huy Panha Updated').toRequestModel(),
        customerId: createResult.id,
      );
      results['updateCustomerResult'] = updateCustomerResult;

      // Create a customer address
      final createAddress = await merchant.customer.createAddress(
        MxCreateCustomerAddressRequestModel(
          customerId: createResult.id,
          address1: createResult.address1,
          address2: createResult.address2,
          city: createResult.city,
          state: createResult.state,
          zip: createResult.zip,
        ),
      );
      results['createAddress'] = createAddress.toJson();

      // Get customer addresses
      final getAddresses = await merchant.customer.getAddress(createResult.id);
      results['getAddresses'] = getAddresses.map((e) => e.toJson()).toList();

      // Update customer addresses
      final updateAddresses = await merchant.customer.updateAddress(createAddress.toRequestModel().copyWith(customerId: createResult.id));
      results['updateAddresses'] = updateAddresses;

      // Add Note to Customer
      final noteAdded = await merchant.customer.addNote(customerId: createResult.id, note: 'Test customer created via API');
      results['addNote'] = noteAdded;

      // Get Customer Notes
      final notes = await merchant.customer.getNote(createResult.id);
      results['getNotes'] = notes.map((e) => e.toJson()).toList();

      // Get Customer Payments
      final payments = await merchant.customer.getPayments(customerId: createResult.id, offset: 0, limit: 10);
      results['getCustomerPayments'] = payments.toJson();

      _setResult(jsonEncode(results));
    } catch (e, s) {
      log("Customer flow failed: $e", stackTrace: s);
      _setError('Customer flow failed: $e');
    } finally {
      _setLoading(false);
    }
  }

  Future<void> _testTerminalTransactionFlow() async {
    if (!_validateCredentials()) return;

    _setLoading(true);
    try {
      final merchant = _createMerchant();
      final results = <String, dynamic>{};

      // Create Terminal Transaction
      final createResult = await merchant.terminal.transaction.create(
        MxTerminalCreateTransactionRequestModel(
          terminalId: '6EAB751C-2235-4FC6-AD18-625A088399D8',
          amount: 10.00,
          type: .sale,
          vaultCard: false,
          replayId: '000000000000001',
        ),
      );
      results['createTransaction'] = createResult.toJson();

      // Get Transaction
      final getResult = await merchant.terminal.transaction.get('000000000000001');
      results['getTransaction'] = getResult.toJson();

      // Update Transaction (example with empty data)
      try {
        await merchant.terminal.transaction.update(
          MxTerminalUpdateTransactionRequestModel(
            reference: 'test_ref',
            terminalId: '6EAB751C-2235-4FC6-AD18-625A088399D8',
            transactionId: 'test_txn_id',
          ),
        );
        results['updateTransaction'] = 'Success';
      } catch (e) {
        results['updateTransaction'] = 'Expected error: $e';
      }

      // Delete Queued Transaction
      try {
        final deleteResult = await merchant.terminal.transaction.deleteQueued('6EAB751C-2235-4FC6-AD18-625A088399D8');
        results['deleteTransaction'] = deleteResult;
      } catch (e) {
        results['deleteTransaction'] = 'Expected error: $e';
      }

      log(jsonEncode(results));
      _setResult(jsonEncode(results));
    } catch (e, s) {
      log("Terminal transaction flow failed: $e", stackTrace: s);
      _setError('Terminal transaction flow failed: $e');
    } finally {
      _setLoading(false);
    }
  }

  Future<void> _testCustomFieldsFlow() async {
    if (!_validateCredentials()) return;

    _setLoading(true);
    try {
      final merchant = _createMerchant();
      final results = <String, dynamic>{};

      // Create Custom Field
      final createResult = await merchant.customer.customField.create(
        MxCreateCustomFieldRequestModel(
          name: 'Customer Rating',
          fieldName: 'customer_rating',
          fieldDataType: .decimal,
          isRequired: false,
          echo: true,
        ),
      );
      results['createCustomField'] = createResult.toJson();

      // Get Custom Fields for a customer
      try {
        final getResults = await merchant.customer.customField.get('test_customer_id');
        results['getCustomFields'] = getResults.map((e) => e.toJson()).toList();
      } catch (e) {
        results['getCustomFields'] = 'Expected error: $e';
      }

      // Delete Custom Field (example)
      try {
        final fieldId = createResult.id;
        final deleteResult = await merchant.customer.customField.delete(fieldId);
        results['deleteCustomField'] = deleteResult;
      } catch (e) {
        results['deleteCustomField'] = 'Expected error: $e';
      }

      log(jsonEncode(results));
      _setResult(jsonEncode(results));
    } catch (e, s) {
      log("Custom fields flow failed: $e", stackTrace: s);
      _setError('Custom fields flow failed: $e');
    } finally {
      _setLoading(false);
    }
  }

  bool _validateCredentials() {
    if (_consumerKeyController.text.trim().isEmpty || _consumerSecretController.text.trim().isEmpty || _merchantIdController.text.trim().isEmpty) {
      _setError('Please fill in all credential fields');
      return false;
    }
    return true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('MX Merchant Example'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
        foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          tabAlignment: .start,
          tabs: const [
            Tab(icon: Icon(Icons.payment), text: 'Payments'),
            Tab(icon: Icon(Icons.people), text: 'Customers'),
            Tab(icon: Icon(Icons.devices), text: 'Terminals'),
            Tab(icon: Icon(Icons.sync_alt), text: 'Transactions'),
            Tab(icon: Icon(Icons.person_outline), text: 'Custom Fields'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [_buildPaymentsTab(), _buildCustomersTab(), _buildTerminalsTab(), _buildTransactionsTab(), _buildCustomFieldsTab()],
      ),
    );
  }

  Widget _buildCredentialsCard() {
    return Card(
      margin: const .all(16),
      child: Padding(
        padding: const .all(16),
        child: Column(
          crossAxisAlignment: .start,
          children: [
            Text('API Credentials', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
            const SizedBox(height: 16),
            TextField(
              controller: _consumerKeyController,
              decoration: const InputDecoration(labelText: 'Consumer Key', hintText: 'Enter your consumer key'),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: _consumerSecretController,
              decoration: const InputDecoration(labelText: 'Consumer Secret', hintText: 'Enter your consumer secret'),
              obscureText: true,
            ),
            const SizedBox(height: 12),
            TextField(
              controller: _merchantIdController,
              decoration: const InputDecoration(labelText: 'Merchant ID', hintText: 'Enter your merchant ID'),
            ),
            const SizedBox(height: 8),
            Text('Using Sandbox Environment', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.primary)),
          ],
        ),
      ),
    );
  }

  Widget _buildPaymentsTab() {
    return SingleChildScrollView(
      child: Column(
        children: [
          _buildCredentialsCard(),
          Card(
            margin: const .all(16),
            child: Padding(
              padding: const .all(16),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  Text('Payment Operations', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                  const SizedBox(height: 8),
                  Text(
                    'Test the complete payment flow including creating payments, retrieving payment data, voiding transactions, and sending receipts.',
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _testPaymentFlow,
                      icon: _isLoading
                          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                          : const Icon(Icons.play_arrow),
                      label: Text(_isLoading ? 'Processing...' : 'Test Payment Flow'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_errorMessage != null) _buildErrorCard(),
          if (_lastResult.isNotEmpty) _buildResultCard(),
        ],
      ),
    );
  }

  Widget _buildCustomersTab() {
    return SingleChildScrollView(
      child: Column(
        children: [
          _buildCredentialsCard(),
          Card(
            margin: const .all(16),
            child: Padding(
              padding: const .all(16),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  Text('Customer Operations', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                  const SizedBox(height: 8),
                  Text(
                    'Test customer management operations including creating customers, retrieving customer data, adding notes, and getting payment history.',
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _testCustomersFlow,
                      icon: _isLoading
                          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                          : const Icon(Icons.people),
                      label: Text(_isLoading ? 'Processing...' : 'Test Customer Flow'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_errorMessage != null) _buildErrorCard(),
          if (_lastResult.isNotEmpty) _buildResultCard(),
        ],
      ),
    );
  }

  Widget _buildTerminalsTab() {
    return SingleChildScrollView(
      child: Column(
        children: [
          _buildCredentialsCard(),
          Card(
            margin: const .all(16),
            child: Padding(
              padding: const .all(16),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  Text('Terminal Operations', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                  const SizedBox(height: 8),
                  Text(
                    'Test terminal management operations including listing terminals and creating new terminals.',
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _testTerminalFlow,
                      icon: _isLoading
                          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                          : const Icon(Icons.devices),
                      label: Text(_isLoading ? 'Processing...' : 'Test Terminal Flow'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_errorMessage != null) _buildErrorCard(),
          if (_lastResult.isNotEmpty) _buildResultCard(),
        ],
      ),
    );
  }

  Widget _buildTransactionsTab() {
    return SingleChildScrollView(
      child: Column(
        children: [
          _buildCredentialsCard(),
          Card(
            margin: const .all(16),
            child: Padding(
              padding: const .all(16),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  Text('Terminal Transaction Operations', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                  const SizedBox(height: 8),
                  Text(
                    'Test terminal transaction operations including creating, updating, retrieving, and deleting terminal transactions.',
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _testTerminalTransactionFlow,
                      icon: _isLoading
                          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                          : const Icon(Icons.sync_alt),
                      label: Text(_isLoading ? 'Processing...' : 'Test Transaction Flow'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_errorMessage != null) _buildErrorCard(),
          if (_lastResult.isNotEmpty) _buildResultCard(),
        ],
      ),
    );
  }

  Widget _buildCustomFieldsTab() {
    return SingleChildScrollView(
      child: Column(
        children: [
          _buildCredentialsCard(),
          Card(
            margin: const .all(16),
            child: Padding(
              padding: const .all(16),
              child: Column(
                crossAxisAlignment: .start,
                children: [
                  Text('Customer Custom Field Operations', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                  const SizedBox(height: 8),
                  Text(
                    'Test customer custom field operations including creating, retrieving, and deleting custom fields for customers.',
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? null : _testCustomFieldsFlow,
                      icon: _isLoading
                          ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
                          : const Icon(Icons.person_outline),
                      label: Text(_isLoading ? 'Processing...' : 'Test Custom Fields Flow'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_errorMessage != null) _buildErrorCard(),
          if (_lastResult.isNotEmpty) _buildResultCard(),
        ],
      ),
    );
  }

  Widget _buildErrorCard() {
    return Card(
      margin: const .all(16),
      color: Theme.of(context).colorScheme.errorContainer,
      child: Padding(
        padding: const .all(16),
        child: Row(
          children: [
            Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
            const SizedBox(width: 12),
            Expanded(
              child: Text(_errorMessage!, style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer)),
            ),
            IconButton(
              onPressed: () => setState(() => _errorMessage = null),
              icon: Icon(Icons.close, color: Theme.of(context).colorScheme.onErrorContainer),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildResultCard() {
    return Card(
      margin: const .all(16),
      child: Padding(
        padding: const .all(16),
        child: Column(
          crossAxisAlignment: .start,
          children: [
            Row(
              spacing: 5,
              children: [
                Expanded(
                  child: Text('API Response', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: .bold)),
                ),
                IconButton(
                  onPressed: () async => await Clipboard.setData(ClipboardData(text: _lastResult)).then((_) {
                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Copied to clipboard')));
                  }),
                  icon: const Icon(Icons.copy),
                ),
                IconButton(onPressed: () => setState(() => _lastResult = ''), icon: const Icon(Icons.clear)),
              ],
            ),
            const SizedBox(height: 12),
            Container(
              width: double.infinity,
              padding: const .all(12),
              decoration: BoxDecoration(color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: .circular(8)),
              child: SingleChildScrollView(
                child: SelectableText(_lastResult, style: Theme.of(context).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'), maxLines: 5),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
3
likes
0
points
178
downloads

Documentation

Documentation

Publisher

unverified uploader

Weekly Downloads

A Flutter/Dart client package for integrating with the MX Merchant REST APIs, including checkout, payments, customers, vaulted accounts, reports, notifications, merchant accounts, and sandbox/production environments.

Homepage
Repository (GitHub)
View/report issues

Topics

#payments #mxmerchant #checkout #rest #gateway

License

unknown (license)

Dependencies

collection, dio, flutter

More

Packages that depend on mx_merchant