all_paystack_payments 1.1.0 copy "all_paystack_payments: ^1.1.0" to clipboard
all_paystack_payments: ^1.1.0 copied to clipboard

A Flutter plugin for integrating Paystack payment services, supporting card payments, bank transfers, and mobile money.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:developer';

import 'package:all_paystack_payments/all_paystack_payments.dart';
import 'package:all_paystack_payments/validation_utils.dart';

import 'l10n/app_localizations.dart';

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

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

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

class _MyAppState extends State<MyApp> {
  Locale _currentLocale = const Locale('en');
  bool _isInitialized = false;
  bool _isInitializing = false;
  bool _isProcessingPayment = false;
  String _statusMessage = 'Not Initialized';
  final String _paystackPublicKey =
      'pk_test_YOUR_PUBLIC_KEY'; // Replace with your actual public key

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

  Future<void> _initializePaystack() async {
    if (_isInitializing) return;

    setState(() {
      _isInitializing = true;
      _statusMessage = 'Initializing Paystack...';
    });

    try {
      await AllPaystackPayments.initialize(_paystackPublicKey);
      setState(() {
        _isInitialized = true;
        _statusMessage = 'Ready to accept payments';
      });
      _showSnackBar(context, 'Paystack initialized successfully');
    } on PaystackError catch (e) {
      setState(() {
        _statusMessage = 'Initialization failed: ${e.message}';
      });
      _showSnackBar(
        context,
        'Initialization failed: ${e.message}',
        isError: true,
      );
    } catch (e) {
      setState(() {
        _statusMessage = 'Unexpected error: ${e.toString()}';
      });
      _showSnackBar(context, 'Unexpected initialization error', isError: true);
    } finally {
      setState(() {
        _isInitializing = false;
      });
    }
  }

  void _showSnackBar(
    BuildContext context,
    String message, {
    bool isError = false,
  }) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError ? Colors.red : Colors.green,
      ),
    );
  }

  void _showPaymentDialog(String title, Widget content) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(title),
          content: content,
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text(AppLocalizations.of(context)!.cancel),
            ),
          ],
        );
      },
    );
  }

  Future<void> _handleCardPayment() async {
    final formKey = GlobalKey<FormState>();
    final cardNumberController = TextEditingController();
    final expiryMonthController = TextEditingController();
    final expiryYearController = TextEditingController();
    final cvvController = TextEditingController();
    final cardHolderController = TextEditingController();
    final emailController = TextEditingController();
    final amountController = TextEditingController();

    _showPaymentDialog(
      AppLocalizations.of(context)!.cardPayment,
      Form(
        key: formKey,
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextFormField(
                controller: emailController,
                decoration: InputDecoration(
                  labelText: AppLocalizations.of(context)!.email,
                ),
                validator: (value) => value?.isEmpty ?? true
                    ? AppLocalizations.of(context)!.required
                    : null,
              ),
              TextFormField(
                controller: amountController,
                decoration: InputDecoration(
                  labelText: AppLocalizations.of(context)!.amountKobo,
                ),
                keyboardType: TextInputType.number,
                validator: (value) => value?.isEmpty ?? true
                    ? AppLocalizations.of(context)!.required
                    : null,
              ),
              TextFormField(
                controller: cardNumberController,
                decoration: InputDecoration(
                  labelText: AppLocalizations.of(context)!.cardNumber,
                ),
                keyboardType: TextInputType.number,
                validator: (value) {
                  if (value?.isEmpty ?? true)
                    return AppLocalizations.of(context)!.required;
                  if (!ValidationUtils.isValidCardNumber(value!))
                    return AppLocalizations.of(context)!.invalidCardNumber;
                  return null;
                },
              ),
              Row(
                children: [
                  Expanded(
                    child: TextFormField(
                      controller: expiryMonthController,
                      decoration: InputDecoration(
                        labelText: AppLocalizations.of(context)!.expiryMonth,
                      ),
                      keyboardType: TextInputType.number,
                      validator: (value) {
                        if (value?.isEmpty ?? true)
                          return AppLocalizations.of(context)!.required;
                        if (!ValidationUtils.isValidExpiryDate(
                          value!,
                          expiryYearController.text,
                        ))
                          return 'Invalid expiry date';
                        return null;
                      },
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: TextFormField(
                      controller: expiryYearController,
                      decoration: InputDecoration(
                        labelText: AppLocalizations.of(context)!.expiryYear,
                      ),
                      keyboardType: TextInputType.number,
                      validator: (value) {
                        if (value?.isEmpty ?? true)
                          return AppLocalizations.of(context)!.required;
                        if (!ValidationUtils.isValidExpiryDate(
                          expiryMonthController.text,
                          value!,
                        ))
                          return 'Invalid expiry date';
                        return null;
                      },
                    ),
                  ),
                ],
              ),
              TextFormField(
                controller: cvvController,
                decoration: InputDecoration(
                  labelText: AppLocalizations.of(context)!.cvv,
                ),
                keyboardType: TextInputType.number,
                validator: (value) {
                  if (value?.isEmpty ?? true)
                    return AppLocalizations.of(context)!.required;
                  if (!ValidationUtils.isValidCvv(value!))
                    return 'Invalid CVV (3-4 digits)';
                  return null;
                },
              ),
              TextFormField(
                controller: cardHolderController,
                decoration: InputDecoration(
                  labelText: AppLocalizations.of(context)!.cardHolderName,
                ),
                validator: (value) => value?.isEmpty ?? true
                    ? AppLocalizations.of(context)!.required
                    : null,
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () async {
                  if (formKey.currentState?.validate() ?? false) {
                    Navigator.of(context).pop();
                    await _processCardPayment(
                      email: emailController.text,
                      amount: int.parse(amountController.text),
                      cardNumber: cardNumberController.text,
                      expiryMonth: expiryMonthController.text,
                      expiryYear: expiryYearController.text,
                      cvv: cvvController.text,
                      cardHolderName: cardHolderController.text,
                    );
                  }
                },
                child: Text(AppLocalizations.of(context)!.pay),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _processCardPayment({
    required String email,
    required int amount,
    required String cardNumber,
    required String expiryMonth,
    required String expiryYear,
    required String cvv,
    required String cardHolderName,
  }) async {
    if (_isProcessingPayment) return;

    setState(() => _isProcessingPayment = true);

    try {
      _showSnackBar(context, 'Processing card payment...');

      final metadata = {
        'payment_type': 'card',
        'timestamp': DateTime.now().toIso8601String(),
        'card_last_four': cardNumber.substring(cardNumber.length - 4),
      };

      final response = await AllPaystackPayments.initializeCardPayment(
        amount: amount,
        email: email,
        cardNumber: cardNumber,
        expiryMonth: expiryMonth,
        expiryYear: expiryYear,
        cvv: cvv,
        cardHolderName: cardHolderName,
        reference: 'card_${DateTime.now().millisecondsSinceEpoch}',
        metadata: metadata,
      );

      if (!mounted) return;

      if (response.isSuccessful) {
        _showSnackBar(
          context,
          'Payment successful! Reference: ${response.reference}',
        );
        // In production, you would navigate to success screen
        // or update order status
      } else {
        _showSnackBar(
          context,
          'Payment failed: ${response.gatewayResponse ?? 'Unknown error'}',
          isError: true,
        );
      }
    } on PaystackError catch (e) {
      if (!mounted) return;
      log('Paystack card payment error: ${e.message}', error: e);

      String userMessage;
      switch (e.code) {
        case 'insufficient_funds':
          userMessage = 'Insufficient funds on card';
          break;
        case 'card_declined':
          userMessage = 'Card was declined. Please try a different card.';
          break;
        case 'invalid_card':
          userMessage = 'Invalid card details. Please check and try again.';
          break;
        case 'expired_card':
          userMessage = 'Card has expired. Please use a valid card.';
          break;
        default:
          userMessage = e.message;
      }

      _showSnackBar(context, userMessage, isError: true);
    } on ArgumentError catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        'Invalid card details: ${e.message}',
        isError: true,
      );
    } on TimeoutException catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        'Payment timed out. Please try again.',
        isError: true,
      );
    } catch (e) {
      if (!mounted) return;
      log('Unexpected card payment error: $e');
      _showSnackBar(
        context,
        'An unexpected error occurred. Please try again.',
        isError: true,
      );
    } finally {
      if (mounted) {
        setState(() => _isProcessingPayment = false);
      }
    }
  }

  Future<void> _handleBankTransfer() async {
    final formKey = GlobalKey<FormState>();
    final emailController = TextEditingController();
    final amountController = TextEditingController();
    Currency selectedCurrency = Currency.ngn;

    _showPaymentDialog(
      AppLocalizations.of(context)!.bankTransfer,
      Form(
        key: formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: emailController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)?.email ?? 'Email',
              ),
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)?.required ?? 'Required'
                  : null,
            ),
            TextFormField(
              controller: amountController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.amountKobo,
              ),
              keyboardType: TextInputType.number,
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            DropdownButtonFormField<Currency>(
              initialValue: selectedCurrency,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.currency,
              ),
              items: Currency.values.map((currency) {
                return DropdownMenuItem(
                  value: currency,
                  child: Text(currency.name.toUpperCase()),
                );
              }).toList(),
              onChanged: (value) => selectedCurrency = value!,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                if (formKey.currentState?.validate() ?? false) {
                  Navigator.of(context).pop();
                  await _processBankTransfer(
                    email: emailController.text,
                    amount: int.parse(amountController.text),
                    currency: selectedCurrency,
                  );
                }
              },
              child: Text(AppLocalizations.of(context)!.initiateTransfer),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _processBankTransfer({
    required String email,
    required int amount,
    required Currency currency,
  }) async {
    if (_isProcessingPayment) return;

    setState(() => _isProcessingPayment = true);

    try {
      _showSnackBar(context, 'Initiating bank transfer...');

      final response = await AllPaystackPayments.initializeBankTransfer(
        amount: amount,
        email: email,
        currency: currency,
        reference: 'bank_transfer_${DateTime.now().millisecondsSinceEpoch}',
        metadata: {
          'payment_type': 'bank_transfer',
          'initiated_at': DateTime.now().toIso8601String(),
        },
      );

      if (!mounted) return;

      if (response.isSuccessful) {
        _showSnackBar(context, 'Bank transfer initiated successfully!');
        // Show bank details dialog
        _showBankTransferDetails(response);
      } else {
        _showSnackBar(
          context,
          'Bank transfer failed: ${response.gatewayResponse ?? 'Unknown error'}',
          isError: true,
        );
      }
    } on PaystackError catch (e) {
      if (!mounted) return;
      log('Bank transfer error: ${e.message}', error: e);
      _showSnackBar(
        context,
        'Bank transfer failed: ${e.message}',
        isError: true,
      );
    } catch (e) {
      if (!mounted) return;
      log('Unexpected bank transfer error: $e');
      _showSnackBar(
        context,
        'An unexpected error occurred. Please try again.',
        isError: true,
      );
    } finally {
      if (mounted) {
        setState(() => _isProcessingPayment = false);
      }
    }
  }

  void _showBankTransferDetails(PaymentResponse response) {
    final rawData = response.rawResponse;
    String bankName = 'Unknown Bank';
    String accountNumber = 'Unknown';
    String accountName = 'Unknown';

    if (rawData != null && rawData['data'] != null) {
      final data = rawData['data'];
      bankName = data['bank_name'] ?? bankName;
      accountNumber = data['account_number'] ?? accountNumber;
      accountName = data['account_name'] ?? accountName;
    }

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Bank Transfer Details'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Bank: $bankName'),
              SizedBox(height: 8),
              Text('Account Name: $accountName'),
              SizedBox(height: 8),
              Text('Account Number: $accountNumber'),
              SizedBox(height: 8),
              Text('Amount: ₦${(response.amount / 100).toStringAsFixed(2)}'),
              SizedBox(height: 8),
              Text('Reference: ${response.reference}'),
              SizedBox(height: 16),
              Text(
                'Please transfer the exact amount to the account above within 30 minutes. The payment will be verified automatically.',
                style: TextStyle(fontSize: 12, color: Colors.grey[600]),
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
                // Optionally navigate to verification screen
                _showVerificationDialog(response.reference);
              },
              child: Text('Verify Payment'),
            ),
          ],
        );
      },
    );
  }

  void _showVerificationDialog(String reference) {
    final referenceController = TextEditingController(text: reference);

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Verify Payment'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('Enter the payment reference to verify:'),
              SizedBox(height: 16),
              TextField(
                controller: referenceController,
                decoration: InputDecoration(
                  labelText: 'Reference',
                  border: OutlineInputBorder(),
                ),
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
                _processVerifyPayment(reference: referenceController.text);
              },
              child: Text('Verify'),
            ),
          ],
        );
      },
    );
  }

  Future<void> _handleMobileMoney() async {
    final formKey = GlobalKey<FormState>();
    final emailController = TextEditingController();
    final amountController = TextEditingController();
    final phoneController = TextEditingController();
    MobileMoneyProvider selectedProvider = MobileMoneyProvider.mpesa;
    Currency selectedCurrency = Currency.ngn;

    _showPaymentDialog(
      AppLocalizations.of(context)?.mobileMoney ?? 'Mobile Money',
      Form(
        key: formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: emailController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.email,
              ),
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            TextFormField(
              controller: amountController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.amountKobo,
              ),
              keyboardType: TextInputType.number,
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            DropdownButtonFormField<Currency>(
              initialValue: selectedCurrency,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.currency,
              ),
              items: Currency.values.map((currency) {
                return DropdownMenuItem(
                  value: currency,
                  child: Text(currency.name.toUpperCase()),
                );
              }).toList(),
              onChanged: (value) => selectedCurrency = value!,
            ),
            TextFormField(
              controller: phoneController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.phoneNumber,
              ),
              validator: (value) {
                if (value?.isEmpty ?? true)
                  return AppLocalizations.of(context)!.required;
                final regex = RegExp(r'^\+\d{10,15}$');
                if (!regex.hasMatch(value!)) {
                  return AppLocalizations.of(context)!.invalidPhoneNumber;
                }
                return null;
              },
            ),
            DropdownButtonFormField<MobileMoneyProvider>(
              initialValue: selectedProvider,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.provider,
              ),
              items: MobileMoneyProvider.values.map((provider) {
                return DropdownMenuItem(
                  value: provider,
                  child: Text(provider.name.toUpperCase()),
                );
              }).toList(),
              onChanged: (value) => selectedProvider = value!,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                if (formKey.currentState?.validate() ?? false) {
                  Navigator.of(context).pop();
                  await _processMobileMoney(
                    email: emailController.text,
                    amount: int.parse(amountController.text),
                    provider: selectedProvider,
                    phoneNumber: phoneController.text,
                    currency: selectedCurrency,
                    localizations: AppLocalizations.of(context)!,
                  );
                }
              },
              child: Text(AppLocalizations.of(context)!.pay),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _processMobileMoney({
    required String email,
    required int amount,
    required MobileMoneyProvider provider,
    required String phoneNumber,
    required Currency currency,
    required AppLocalizations localizations,
  }) async {
    if (_isProcessingPayment) return;

    setState(() => _isProcessingPayment = true);

    try {
      _showSnackBar(context, 'Processing mobile money payment...');

      // Validate phone number format for provider
      if (phoneNumber.startsWith('+254') &&
          provider != MobileMoneyProvider.mpesa) {
        throw ArgumentError(
          'Only M-Pesa is supported for Kenyan numbers (+254)',
        );
      }

      final response = await AllPaystackPayments.initializeMobileMoney(
        amount: amount,
        email: email,
        provider: provider,
        phoneNumber: phoneNumber,
        currency: currency,
        reference: 'mobile_money_${DateTime.now().millisecondsSinceEpoch}',
        metadata: {
          'payment_type': 'mobile_money',
          'provider': provider.name,
          'phone_last_four': phoneNumber.substring(phoneNumber.length - 4),
        },
      );

      if (!mounted) return;

      if (response.isSuccessful) {
        _showSnackBar(
          context,
          'Mobile money payment initiated! Check your phone for the prompt.',
        );
        // Show instructions for mobile money payment
        _showMobileMoneyInstructions(provider);
      } else {
        _showSnackBar(
          context,
          'Mobile money payment failed: ${response.gatewayResponse ?? 'Unknown error'}',
          isError: true,
        );
      }
    } on PaystackError catch (e) {
      if (!mounted) return;
      log('Mobile money error: ${e.message}', error: e);

      String userMessage;
      switch (e.code) {
        case 'insufficient_balance':
          userMessage = 'Insufficient mobile money balance';
          break;
        case 'invalid_phone':
          userMessage = 'Invalid phone number for this provider';
          break;
        case 'unsupported_network':
          userMessage = 'Mobile network not supported for this provider';
          break;
        default:
          userMessage = e.message;
      }

      _showSnackBar(context, userMessage, isError: true);
    } on ArgumentError catch (e) {
      if (!mounted) return;
      _showSnackBar(context, e.message, isError: true);
    } catch (e) {
      if (!mounted) return;
      log('Unexpected mobile money error: $e');
      _showSnackBar(
        context,
        'An unexpected error occurred. Please try again.',
        isError: true,
      );
    } finally {
      if (mounted) {
        setState(() => _isProcessingPayment = false);
      }
    }
  }

  void _showMobileMoneyInstructions(MobileMoneyProvider provider) {
    String instructions = '';

    switch (provider) {
      case MobileMoneyProvider.mpesa:
        instructions =
            '1. Check your phone for the M-Pesa prompt\n2. Enter your M-Pesa PIN\n3. Confirm the payment\n4. Payment will be verified automatically';
        break;
      case MobileMoneyProvider.airtel:
        instructions =
            '1. Check your phone for the Airtel Money prompt\n2. Enter your Airtel Money PIN\n3. Confirm the payment\n4. Payment will be verified automatically';
        break;
      case MobileMoneyProvider.vodafone:
        instructions =
            '1. Check your phone for the Vodafone Cash prompt\n2. Enter your Vodafone Cash PIN\n3. Confirm the payment\n4. Payment will be verified automatically';
        break;
      case MobileMoneyProvider.tigo:
        instructions =
            '1. Check your phone for the Tigo Cash prompt\n2. Enter your Tigo Cash PIN\n3. Confirm the payment\n4. Payment will be verified automatically';
        break;
    }

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('${provider.name.toUpperCase()} Payment Instructions'),
          content: Text(instructions),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: Text('OK'),
            ),
          ],
        );
      },
    );
  }

  Future<void> _handleVerifyPayment() async {
    final formKey = GlobalKey<FormState>();
    final referenceController = TextEditingController();

    _showPaymentDialog(
      AppLocalizations.of(context)!.verifyPayment,
      Form(
        key: formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: referenceController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.reference,
              ),
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                if (formKey.currentState?.validate() ?? false) {
                  Navigator.of(context).pop();
                  await _processVerifyPayment(
                    reference: referenceController.text,
                  );
                }
              },
              child: Text(AppLocalizations.of(context)!.verify),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _processVerifyPayment({required String reference}) async {
    final currentContext = context;
    try {
      _showSnackBar(
        currentContext,
        AppLocalizations.of(currentContext)!.verifyingPayment,
      );
      final response = await AllPaystackPayments.verifyPayment(reference);
      if (!mounted) return;
      _showSnackBar(
        context,
        AppLocalizations.of(
          context,
        )!.paymentStatus(response.status.name, response.gatewayResponse ?? ''),
      );
    } on PaystackError catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        AppLocalizations.of(context)!.verificationFailed(e.message),
        isError: true,
      );
    } catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        AppLocalizations.of(context)!.unexpectedError(e.toString()),
        isError: true,
      );
    }
  }

  Future<void> _handleGetPaymentStatus() async {
    final formKey = GlobalKey<FormState>();
    final referenceController = TextEditingController();

    _showPaymentDialog(
      AppLocalizations.of(context)!.getPaymentStatus,
      Form(
        key: formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: referenceController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.reference,
              ),
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                if (formKey.currentState?.validate() ?? false) {
                  Navigator.of(context).pop();
                  await _processGetPaymentStatus(
                    reference: referenceController.text,
                  );
                }
              },
              child: Text(AppLocalizations.of(context)!.getStatus),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _processGetPaymentStatus({required String reference}) async {
    try {
      _showSnackBar(context, 'Getting payment status...');
      final response = await AllPaystackPayments.getPaymentStatus(reference);
      if (!mounted) return;

      String statusMessage;
      String currencySymbol = '₦'; // Default to Naira

      switch (response.currency) {
        case Currency.usd:
          currencySymbol = '\$';
          break;
        case Currency.ghs:
          currencySymbol = '₵';
          break;
        case Currency.zar:
          currencySymbol = 'R';
          break;
        case Currency.kes:
          currencySymbol = 'KSh';
          break;
        default:
          currencySymbol = '₦';
      }

      switch (response.status) {
        case PaymentStatus.success:
          statusMessage = 'Payment completed successfully';
          break;
        case PaymentStatus.failed:
          statusMessage = 'Payment failed';
          break;
        case PaymentStatus.pending:
          statusMessage = 'Payment is still pending';
          break;
        case PaymentStatus.cancelled:
          statusMessage = 'Payment was cancelled';
          break;
      }

      _showSnackBar(
        context,
        'Status: $statusMessage (Amount: $currencySymbol${(response.amount / 100).toStringAsFixed(2)})',
      );
    } on PaystackError catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        'Failed to get status: ${e.message}',
        isError: true,
      );
    } catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        'Unexpected error: ${e.toString()}',
        isError: true,
      );
    }
  }

  Future<void> _handleCancelPayment() async {
    final formKey = GlobalKey<FormState>();
    final referenceController = TextEditingController();

    _showPaymentDialog(
      AppLocalizations.of(context)!.cancelPayment,
      Form(
        key: formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: referenceController,
              decoration: InputDecoration(
                labelText: AppLocalizations.of(context)!.reference,
              ),
              validator: (value) => value?.isEmpty ?? true
                  ? AppLocalizations.of(context)!.required
                  : null,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                if (formKey.currentState?.validate() ?? false) {
                  Navigator.of(context).pop();
                  await _processCancelPayment(
                    reference: referenceController.text,
                  );
                }
              },
              child: Text(AppLocalizations.of(context)!.cancel),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _processCancelPayment({required String reference}) async {
    final currentContext = context;
    try {
      _showSnackBar(
        currentContext,
        AppLocalizations.of(currentContext)!.cancellingPayment,
      );
      final success = await AllPaystackPayments.cancelPayment(reference);
      if (!mounted) return;
      _showSnackBar(
        context,
        success
            ? AppLocalizations.of(context)!.paymentCancelledSuccessfully
            : AppLocalizations.of(context)!.paymentCancellationFailed,
      );
    } on PaystackError catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        AppLocalizations.of(context)!.cancellationFailed(e.message),
        isError: true,
      );
    } catch (e) {
      if (!mounted) return;
      _showSnackBar(
        context,
        AppLocalizations.of(context)!.unexpectedError(e.toString()),
        isError: true,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      locale: _currentLocale,
      home: Scaffold(
        appBar: AppBar(
          title: Text(AppLocalizations.of(context)!.appTitle),
          actions: [
            DropdownButton<Locale>(
              value: _currentLocale,
              onChanged: (Locale? newLocale) {
                if (newLocale != null) {
                  setState(() {
                    _currentLocale = newLocale;
                  });
                }
              },
              items: const [
                DropdownMenuItem(value: Locale('en'), child: Text('English')),
                DropdownMenuItem(value: Locale('fr'), child: Text('Français')),
              ],
            ),
          ],
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text(
                _isInitialized
                    ? AppLocalizations.of(context)?.readyToAcceptPayments ??
                          'Ready to accept payments'
                    : AppLocalizations.of(context)?.initializing ??
                          'Initializing...',
                style: TextStyle(
                  color: _isInitialized ? Colors.green : Colors.orange,
                  fontWeight: FontWeight.bold,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 8),
              Text(_statusMessage, textAlign: TextAlign.center),
              const SizedBox(height: 32),
              ElevatedButton(
                onPressed: (_isInitialized && !_isProcessingPayment)
                    ? _handleCardPayment
                    : null,
                child: _isProcessingPayment
                    ? SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(
                          strokeWidth: 2,
                          valueColor: AlwaysStoppedAnimation<Color>(
                            Colors.white,
                          ),
                        ),
                      )
                    : Text(
                        AppLocalizations.of(context)?.payWithCard ??
                            'Pay with Card',
                      ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: (_isInitialized && !_isProcessingPayment)
                    ? _handleBankTransfer
                    : null,
                child: _isProcessingPayment
                    ? SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(
                          strokeWidth: 2,
                          valueColor: AlwaysStoppedAnimation<Color>(
                            Colors.white,
                          ),
                        ),
                      )
                    : Text(
                        AppLocalizations.of(context)?.payWithBankTransfer ??
                            'Pay with Bank Transfer',
                      ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: (_isInitialized && !_isProcessingPayment)
                    ? _handleMobileMoney
                    : null,
                child: _isProcessingPayment
                    ? SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(
                          strokeWidth: 2,
                          valueColor: AlwaysStoppedAnimation<Color>(
                            Colors.white,
                          ),
                        ),
                      )
                    : Text(
                        AppLocalizations.of(context)?.payWithMobileMoney ??
                            'Pay with Mobile Money',
                      ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isInitialized ? _handleVerifyPayment : null,
                child: Text(
                  AppLocalizations.of(context)?.verifyPayment ??
                      'Verify Payment',
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isInitialized ? _handleGetPaymentStatus : null,
                child: Text(
                  AppLocalizations.of(context)?.getPaymentStatus ??
                      'Get Payment Status',
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isInitialized ? _handleCancelPayment : null,
                child: Text(
                  AppLocalizations.of(context)?.cancelPayment ??
                      'Cancel Payment',
                ),
              ),
              const SizedBox(height: 32),
              Text(
                'Payment Management',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                  color: Colors.blue[800],
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isInitialized ? _handleGetPaymentStatus : null,
                style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
                child: Text(
                  AppLocalizations.of(context)?.getPaymentStatus ??
                      'Get Payment Status',
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _isInitialized ? _handleVerifyPayment : null,
                style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                child: Text(
                  AppLocalizations.of(context)?.verifyPayment ??
                      'Verify Payment',
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
160
points
259
downloads

Publisher

verified publisherxeplas.com

Weekly Downloads

A Flutter plugin for integrating Paystack payment services, supporting card payments, bank transfers, and mobile money.

Homepage
Repository (GitHub)
View/report issues

Topics

#paystack #payments #flutter #mobile-money #api

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, http, plugin_platform_interface, web, webview_flutter

More

Packages that depend on all_paystack_payments

Packages that implement all_paystack_payments