korapay_flutter 0.3.1 copy "korapay_flutter: ^0.3.1" to clipboard
korapay_flutter: ^0.3.1 copied to clipboard

A Flutter SDK for integrating Korapay payments, offering card, bank transfer, and direct bank payment methods with a beautiful UI and easy integration.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:developer' as developer;
import 'package:korapay_flutter/korapay_flutter.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter/services.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      statusBarIconBrightness: Brightness.dark,
    ),
  );
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Korapay Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: const Color(0xFF49ceb0),
        colorScheme: ColorScheme.light(
          primary: const Color(0xFF49ceb0),
          secondary: const Color(0xFF989898),
        ),
        scaffoldBackgroundColor: Colors.white,
        textTheme: const TextTheme(
          titleLarge: TextStyle(fontWeight: FontWeight.w600),
          titleMedium: TextStyle(fontWeight: FontWeight.w500),
          labelLarge: TextStyle(fontWeight: FontWeight.w500),
        ),
        inputDecorationTheme: InputDecorationTheme(
          filled: true,
          fillColor: Colors.grey.shade50,
          contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(8),
            borderSide: BorderSide.none,
          ),
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(8),
            borderSide: BorderSide.none,
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(8),
            borderSide: const BorderSide(color: Color(0xFF49ceb0), width: 1.5),
          ),
          labelStyle: TextStyle(color: Colors.grey.shade700),
          hintStyle: TextStyle(color: Colors.grey.shade500, fontSize: 14),
          floatingLabelStyle: const TextStyle(color: Color(0xFF49ceb0)),
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            backgroundColor: const Color(0xFF49ceb0),
            foregroundColor: Colors.white,
            elevation: 0,
            padding: const EdgeInsets.symmetric(vertical: 16),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
            textStyle: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),
        cardTheme: CardTheme(
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
            side: BorderSide(color: Colors.grey.shade200),
          ),
          color: Colors.white,
        ),
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.white,
          foregroundColor: Color(0xFF989898),
          elevation: 0,
          systemOverlayStyle: SystemUiOverlayStyle.dark,
        ),
        snackBarTheme: SnackBarThemeData(
          behavior: SnackBarBehavior.floating,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8),
          ),
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _amountController = TextEditingController(text: '1000');
  final _nameController = TextEditingController(text: 'John Doe');
  final _emailController = TextEditingController(text: 'john.doe@example.com');
  String _selectedCurrency = 'NGN';
  String _selectedPaymentMethod = 'card';
  bool _isLiveMode = false;
  
  KorapayResponse? _lastResponse;
  bool _isLoading = false;

  @override
  void dispose() {
    _amountController.dispose();
    _nameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  Future<void> _initiatePayment() async {
    // Log the pay now button press
    developer.log('Pay Now button pressed', name: 'KorapayExample');
    
    // Input validation
    if (_amountController.text.isEmpty) {
      _showErrorSnackBar('Please enter a valid amount');
      return;
    }
    
    if (_nameController.text.isEmpty) {
      _showErrorSnackBar('Please enter your name');
      return;
    }
    
    if (_emailController.text.isEmpty || !_emailController.text.contains('@')) {
      _showErrorSnackBar('Please enter a valid email address');
      return;
    }
    
    setState(() {
      _isLoading = true;
    });

    try {
      // Generate a unique reference
      final reference = 'flutter_sdk_${DateTime.now().millisecondsSinceEpoch}';
      developer.log('Generated reference: $reference', name: 'KorapayExample');
      
      // Create a configuration for the payment
      final config = KorapayConfig(
        key: _isLiveMode 
            ? 'pk_live_XYZ123456789' // Replace with your live key
            : 'pk_test_4wrQJedRa2GhJsiHFdtEYBN5EWrDSDviZpmuA1sJ', // Test key
        reference: reference,
        amount: double.parse(_amountController.text),
        currency: _selectedCurrency,
        customer: KorapayCustomer(
          name: _nameController.text,
          email: _emailController.text,
        ),
        title: 'Payment for Product',
        description: 'Test payment from Flutter SDK',
        channels: [_selectedPaymentMethod], // Use selected payment method
      );
      
      developer.log('Initializing payment with config: ${config.toJson()}', name: 'KorapayExample');

      // Initialize the payment
      developer.log('Calling KorapayCheckout.checkoutPayment', name: 'KorapayExample');
      final response = await KorapayCheckout.checkoutPayment(
        context,
        config,
        closeOnSuccess: true,
      );
      
      // Log the response
      developer.log('Payment response received: ${response.status}', name: 'KorapayExample');
      developer.log('Response details: Reference=${response.reference}, Error=${response.errorMessage}', 
        name: 'KorapayExample');
      
      // Show a response message
      _showResponseSnackBar(response);
      
      // Update state with new response
      setState(() {
        _lastResponse = response;
        _isLoading = false;
      });
      
    } catch (e) {
      // Log any errors
      developer.log('Payment error: $e', name: 'KorapayExample', error: e);
      
      setState(() {
        _lastResponse = KorapayResponse.failed(e.toString());
        _isLoading = false;
      });
      
      // Show error
      _showErrorSnackBar('Error: ${e.toString()}');
    }
  }
  
  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Row(
          children: [
            const Icon(Icons.error_outline, color: Colors.white),
            const SizedBox(width: 8),
            Expanded(child: Text(message)),
          ],
        ),
        backgroundColor: Colors.red.shade700,
        behavior: SnackBarBehavior.floating,
        duration: const Duration(seconds: 4),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        margin: const EdgeInsets.all(16),
        action: SnackBarAction(
          label: 'DISMISS',
          textColor: Colors.white,
          onPressed: () {
            ScaffoldMessenger.of(context).hideCurrentSnackBar();
          },
        ),
      ),
    );
  }
  
  void _showResponseSnackBar(KorapayResponse response) {
    final IconData icon;
    final Color backgroundColor;
    final String message;
    
    switch (response.status) {
      case KorapayStatus.success:
        icon = Icons.check_circle_outline;
        backgroundColor = Colors.green.shade700;
        message = 'Payment successful!';
        break;
      case KorapayStatus.cancelled:
        icon = Icons.cancel_outlined;
        backgroundColor = Colors.orange.shade700;
        message = 'Payment was cancelled';
        break;
      case KorapayStatus.failed:
        icon = Icons.error_outline;
        backgroundColor = Colors.red.shade700;
        message = 'Payment failed: ${response.errorMessage ?? "Unknown error"}';
        break;
      default:
        icon = Icons.info_outline;
        backgroundColor = Colors.blue.shade700;
        message = 'Payment processing';
    }
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Row(
          children: [
            Icon(icon, color: Colors.white),
            const SizedBox(width: 8),
            Expanded(child: Text(message)),
          ],
        ),
        backgroundColor: backgroundColor,
        behavior: SnackBarBehavior.floating,
        duration: const Duration(seconds: 4),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        margin: const EdgeInsets.all(16),
        action: SnackBarAction(
          label: 'DISMISS',
          textColor: Colors.white,
          onPressed: () {
            ScaffoldMessenger.of(context).hideCurrentSnackBar();
          },
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            SvgPicture.network(
              'https://korablobstorage.blob.core.windows.net/modal-bucket/Safety.svg',
              width: 20,
              height: 20,
              placeholderBuilder: (context) => const Icon(
                Icons.security,
                size: 20,
                color: Color(0xFF49ceb0),
              ),
              colorFilter: const ColorFilter.mode(Color(0xFF49ceb0), BlendMode.srcIn),
            ),
            const SizedBox(width: 8),
            const Text.rich(
              TextSpan(
                children: [
                  TextSpan(
                    text: 'Secured by ',
                    style: TextStyle(color: Color(0xFF989898)),
                  ),
                  TextSpan(
                    text: 'Kora',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Color(0xFF989898),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
        centerTitle: true,
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Mode Toggle (Test/Live)
              Container(
                padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
                decoration: BoxDecoration(
                  color: _isLiveMode ? Colors.green.shade50 : Colors.amber.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(
                    color: _isLiveMode ? Colors.green.shade200 : Colors.amber.shade200,
                  ),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Row(
                      children: [
                        Icon(
                          _isLiveMode ? Icons.check_circle : Icons.warning_amber_rounded,
                          color: _isLiveMode ? Colors.green.shade700 : Colors.amber.shade700,
                          size: 18,
                        ),
                        const SizedBox(width: 8),
                        Text(
                          _isLiveMode ? 'Live Mode' : 'Test Mode',
                          style: TextStyle(
                            fontWeight: FontWeight.w600,
                            color: _isLiveMode ? Colors.green.shade700 : Colors.amber.shade700,
                          ),
                        ),
                      ],
                    ),
                    Switch(
                      value: _isLiveMode,
                      activeColor: Colors.green.shade600,
                      activeTrackColor: Colors.green.shade100,
                      inactiveThumbColor: Colors.amber.shade600,
                      inactiveTrackColor: Colors.amber.shade100,
                      onChanged: (value) {
                        setState(() {
                          _isLiveMode = value;
                        });
                      },
                    ),
                  ],
                ),
              ),
              
              Expanded(
                child: SingleChildScrollView(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const SizedBox(height: 20),
                      
                      const Text(
                        'Payment Details',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                          color: Color(0xFF333333),
                        ),
                      ),
                      const SizedBox(height: 16),
                      
                      // Amount input
                      TextField(
                        controller: _amountController,
                        decoration: const InputDecoration(
                          labelText: 'Amount',
                          prefixIcon: Icon(Icons.payments_outlined),
                        ),
                        keyboardType: TextInputType.number,
                      ),
                      const SizedBox(height: 16),
            
                      // Currency selection
                      const Text(
                        'Currency',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w500,
                          color: Color(0xFF666666),
                        ),
                      ),
                      const SizedBox(height: 8),
                      _buildCurrencySelector(),
                      const SizedBox(height: 20),
                      
                      // Payment Methods Section
                      const Text(
                        'Payment Method',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w500,
                          color: Color(0xFF666666),
                        ),
                      ),
                      const SizedBox(height: 12),
                      _buildPaymentMethodSelector(),
                      const SizedBox(height: 20),
            
                      // Personal information
                      const Text(
                        'Personal Information',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w500,
                          color: Color(0xFF666666),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _nameController,
                        decoration: const InputDecoration(
                          labelText: 'Full Name',
                          prefixIcon: Icon(Icons.person_outline),
                        ),
                      ),
                      const SizedBox(height: 12),
                      TextField(
                        controller: _emailController,
                        decoration: const InputDecoration(
                          labelText: 'Email Address',
                          prefixIcon: Icon(Icons.email_outlined),
                        ),
                        keyboardType: TextInputType.emailAddress,
                      ),
                      
                      // Last transaction result
                      const SizedBox(height: 24),
                      if (_lastResponse != null)
                        _buildTransactionResult(_lastResponse!),
                      const SizedBox(height: 24),
                    ],
                  ),
                ),
              ),
              
              const SizedBox(height: 16),
              
              // Pay Now Button
              ElevatedButton(
                onPressed: _isLoading ? null : _initiatePayment,
                style: ElevatedButton.styleFrom(
                  minimumSize: const Size(double.infinity, 48),
                  backgroundColor: _isLiveMode ? Colors.green.shade600 : const Color(0xFF49ceb0),
                ),
                child: _isLoading
                    ? const SizedBox(
                        width: 24,
                        height: 24,
                        child: CircularProgressIndicator(
                          color: Colors.white,
                          strokeWidth: 2,
                        ),
                      )
                    : Text(_isLiveMode ? 'Pay Now (LIVE)' : 'Pay Now (TEST)'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildCurrencySelector() {
    return Row(
      children: [
        _buildCurrencyButton('NGN'),
        const SizedBox(width: 8),
        _buildCurrencyButton('USD'),
        const SizedBox(width: 8),
        _buildCurrencyButton('GHS'),
      ],
    );
  }

  Widget _buildCurrencyButton(String currency) {
    final isSelected = _selectedCurrency == currency;
    return Expanded(
      child: InkWell(
        onTap: () {
          setState(() {
            _selectedCurrency = currency;
          });
        },
        child: Container(
          padding: const EdgeInsets.symmetric(vertical: 12),
          decoration: BoxDecoration(
            color: isSelected ? const Color(0xFF49ceb0) : Colors.grey.shade100,
            borderRadius: BorderRadius.circular(8),
          ),
          child: Center(
            child: Text(
              currency,
              style: TextStyle(
                color: isSelected ? Colors.white : Colors.black87,
                fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildPaymentMethodSelector() {
    return Column(
      children: [
        _buildPaymentMethodOption(
          'card', 
          'Pay with Debit Card', 
          Icons.credit_card,
        ),
        const SizedBox(height: 8),
        _buildPaymentMethodOption(
          'bank_transfer', 
          'Pay with Bank Transfer', 
          Icons.account_balance_outlined,
        ),
        const SizedBox(height: 8),
        _buildPaymentMethodOption(
          'bank', 
          'Pay with Bank', 
          Icons.account_balance,
        ),
      ],
    );
  }

  Widget _buildPaymentMethodOption(String value, String label, IconData icon) {
    final isSelected = _selectedPaymentMethod == value;
    
    return InkWell(
      onTap: () {
        setState(() {
          _selectedPaymentMethod = value;
        });
      },
      borderRadius: BorderRadius.circular(8),
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
        decoration: BoxDecoration(
          color: isSelected ? const Color(0xFF49ceb0).withOpacity(0.1) : Colors.grey.shade50,
          borderRadius: BorderRadius.circular(8),
          border: Border.all(
            color: isSelected ? const Color(0xFF49ceb0) : Colors.grey.shade300,
            width: isSelected ? 2 : 1,
          ),
        ),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: isSelected ? const Color(0xFF49ceb0) : Colors.grey.shade100,
                shape: BoxShape.circle,
              ),
              child: Icon(
                icon,
                color: isSelected ? Colors.white : Colors.grey.shade700,
                size: 20,
              ),
            ),
            const SizedBox(width: 12),
            Text(
              label,
              style: TextStyle(
                fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                color: isSelected ? const Color(0xFF49ceb0) : Colors.black87,
              ),
            ),
            const Spacer(),
            if (isSelected)
              const Icon(
                Icons.check_circle,
                color: Color(0xFF49ceb0),
                size: 20,
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildTransactionResult(KorapayResponse response) {
    final Color statusColor;
    final String statusText;
    final IconData statusIcon;

    switch (response.status) {
      case KorapayStatus.success:
        statusColor = Colors.green.shade600;
        statusText = 'Payment Successful';
        statusIcon = Icons.check_circle;
        break;
      case KorapayStatus.cancelled:
        statusColor = Colors.orange.shade600;
        statusText = 'Payment Cancelled';
        statusIcon = Icons.cancel;
        break;
      case KorapayStatus.failed:
        statusColor = Colors.red.shade600;
        statusText = 'Payment Failed';
        statusIcon = Icons.error;
        break;
      default:
        statusColor = Colors.blue.shade600;
        statusText = 'Payment Pending';
        statusIcon = Icons.pending;
    }

    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: statusColor.withOpacity(0.1),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: statusColor.withOpacity(0.3)),
      ),
      child: Row(
        children: [
          Icon(statusIcon, color: statusColor),
          const SizedBox(width: 8),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  statusText,
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: statusColor,
                  ),
                ),
                if (response.reference != null)
                  Text(
                    'Ref: ${response.reference}',
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.grey.shade700,
                    ),
                  ),
                if (response.errorMessage != null)
                  Text(
                    response.errorMessage!,
                    style: TextStyle(
                      fontSize: 12, 
                      color: Colors.red.shade700,
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
0
points
55
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter SDK for integrating Korapay payments, offering card, bank transfer, and direct bank payment methods with a beautiful UI and easy integration.

Homepage

License

unknown (license)

Dependencies

flutter, flutter_svg, http

More

Packages that depend on korapay_flutter