korapay_flutter 0.3.0
korapay_flutter: ^0.3.0 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,
),
),
],
),
),
],
),
);
}
}