lokotro_pay 1.0.4
lokotro_pay: ^1.0.4 copied to clipboard
Lokotro Pay: A modern Flutter payment plugin with glassmorphism design for seamless payment collection via cards and mobile money.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:lokotro_pay/lokotro_pay.dart';
import 'package:lokotro_pay/flutter_masterpass.dart';
import 'package:lokotro_pay/widgets/mastercard_hosted_session_widget.dart';
import 'package:lokotro_pay/enums/lokotro_pay_theme.dart';
import 'card_hosted_usage_screen.dart';
import 'multi_language_example.dart';
void main() {
runApp(const LokotroPayExampleApp());
}
class LokotroPayExampleApp extends StatelessWidget {
const LokotroPayExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Lokotro Pay Example',
// Add localization support
localizationsDelegates: LokotroLocalizationService.localizationsDelegates,
supportedLocales: LokotroLocalizationService.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.teal,
brightness: Brightness.dark,
scaffoldBackgroundColor: LokotroPayColors.backgroundDark,
appBarTheme: const AppBarTheme(
backgroundColor: LokotroPayColors.primary,
foregroundColor: LokotroPayColors.white,
),
),
home: const PaymentExampleScreen(),
);
}
}
class PaymentExampleScreen extends StatefulWidget {
const PaymentExampleScreen({super.key});
@override
State<PaymentExampleScreen> createState() => _PaymentExampleScreenState();
}
class _PaymentExampleScreenState extends State<PaymentExampleScreen> {
// Your encrypted app-key from the backend (UPDATED - CORRECT KEY)
static const String _defaultAppKey = 'gAAAAABpOEoZG9K1Xz3akngbV5WY4QCZfiAtI71uqi0NuWgs9EHmCUNUFbZ8vSHW-k5nSBIPSiJ6WK39o3JvDS5P7bDNFNE6D482LbxbvFbaQeTLrDRzB9bk9J0I1fyqiheWKlksZiy5gN8xUJeI1sbJa6Ze6nb_37URDrl1xsv8Dy9b0DRaJfwZRf5co4vpxtYmg8tTrS6c';
bool _isProduction = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lokotro Pay Example'),
centerTitle: true,
actions: [
Switch(
value: _isProduction,
onChanged: (value) {
setState(() {
_isProduction = value;
});
},
activeColor: LokotroPayColors.secondary,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildPaymentMethodsGrid(),
const SizedBox(height: 32),
_buildEnvironmentInfo(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const Icon(
Icons.payment,
size: 48,
color: LokotroPayColors.secondary,
),
const SizedBox(height: 16),
const Text(
'Lokotro Pay Pluginx',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose your preferred payment method',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildPaymentMethodsGrid() {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.0,
children: [
_buildPaymentMethodCard(
title: 'Lokotro Wallet',
icon: Icons.account_balance_wallet,
color: LokotroPayColors.primary,
onTap: () => _handleWalletPaymentTap(),
),
_buildPaymentMethodCard(
title: 'Lokotro Flash',
icon: Icons.flash_on,
color: LokotroPayColors.secondary,
onTap: () => _handleFlashPaymentTap(),
),
_buildPaymentMethodCard(
title: 'Mobile Money',
icon: Icons.phone_android,
color: LokotroPayColors.success,
onTap: () => _handleMobileMoneyPaymentTap(),
),
_buildPaymentMethodCard(
title: 'Payment Card',
icon: Icons.credit_card,
color: LokotroPayColors.warning,
onTap: () => _handleCardPaymentTap(),
),
_buildPaymentMethodCard(
title: 'Bank Transfer',
icon: Icons.account_balance,
color: Colors.indigo,
onTap: () => _handleBankTransferTap(),
),
_buildPaymentMethodCard(
title: 'Select Payment Method',
icon: Icons.payment,
color: LokotroPayColors.primary,
onTap: () => _handleAllPaymentMethodsTap(),
),
_buildPaymentMethodCard(
title: 'Multi-Language Demo',
icon: Icons.language,
color: Colors.purple,
onTap: () => _handleMultiLanguageTap(),
),
],
);
}
Widget _buildPaymentMethodCard({
required String title,
required IconData icon,
required Color color,
required VoidCallback onTap,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 32,
color: color,
),
),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
Widget _buildEnvironmentInfo() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
_isProduction ? Icons.cloud : Icons.developer_mode,
color: _isProduction ? LokotroPayColors.success : LokotroPayColors.warning,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isProduction ? 'Production Mode' : 'Development Mode',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
Text(
_isProduction
? 'Using live payment gateway'
: 'Using test payment gateway',
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
),
);
}
void _handleWalletPaymentTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WalletPaymentOptionsScreen(
isProduction: _isProduction,
defaultAppKey: _defaultAppKey,
),
),
);
}
void _handleFlashPaymentTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => FlashPaymentOptionsScreen(
isProduction: _isProduction,
defaultAppKey: _defaultAppKey,
),
),
);
}
void _handleMobileMoneyPaymentTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MobileMoneyPaymentOptionsScreen(
isProduction: _isProduction,
defaultAppKey: _defaultAppKey,
),
),
);
}
void _handleCardPaymentTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => CardHostedUsageScreen(
isProduction: _isProduction,
appKey: _defaultAppKey,
amount: '1.00',
currency: 'USD',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
notifyUrl: 'http://10.230.60.61:6495/notify/status',
),
),
);
}
void _handleBankTransferTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BankTransferOptionsScreen(
isProduction: _isProduction,
defaultAppKey: _defaultAppKey,
),
),
);
}
void _handleAllPaymentMethodsTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AllPaymentMethodsOptionsScreen(
isProduction: _isProduction,
defaultAppKey: _defaultAppKey,
),
),
);
}
void _handleMultiLanguageTap() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MultiLanguageExampleScreen(),
),
);
}
void _handlePaymentMethodTap(String paymentMethod) {
// Show a dialog or navigate to payment configuration
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: LokotroPayColors.cardDark,
title: Text(
'Payment Method Selected',
style: const TextStyle(
color: LokotroPayColors.textPrimaryDark,
fontWeight: FontWeight.bold,
),
),
content: Text(
'You selected: ${_getPaymentMethodDisplayName(paymentMethod)}\n\nThis will open the payment configuration screen.',
style: TextStyle(
color: LokotroPayColors.textSecondaryDark,
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text(
'Cancel',
style: TextStyle(color: LokotroPayColors.textSecondaryDark),
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
_startPaymentFlow(paymentMethod);
},
style: ElevatedButton.styleFrom(
backgroundColor: LokotroPayColors.primary,
foregroundColor: LokotroPayColors.white,
),
child: const Text('Continue'),
),
],
),
);
}
String _getPaymentMethodDisplayName(String paymentMethod) {
switch (paymentMethod) {
case 'wallet':
return 'Lokotro Wallet';
case 'flash':
return 'Lokotro Flash';
case 'mobile_money':
return 'Mobile Money';
case 'card':
return 'Payment Card';
default:
return paymentMethod;
}
}
void _startPaymentFlow(String paymentMethod) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: _isProduction);
// Create payment configuration
final configs = LokotroPayConfigs(
token: _defaultAppKey,
isProduction: _isProduction,
acceptLanguage: 'en', // English for main payment
);
// Create payment body with selected payment method
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: paymentMethod,
userInfo: 'full',
paymentMethodInfo: 'full',
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information
firstName: 'josmid',
lastName: 'joes',
phoneNumber: '2439978540000',
email: '[email protected]',
// Payment method specific information (will be filled in the checkout)
walletNumber: paymentMethod == 'wallet' ? '3005710720108954' : null,
walletPin: paymentMethod == 'wallet' ? '048698' : null,
// Merchant information
merchant: const LokotroMerchantInfo(
name: 'bilokos',
url: 'http://10.230.60.61:6495',
logo: 'http://10.230.60.61:6495/logo',
),
metadata: {
'source': 'flutter_example',
'payment_method': paymentMethod,
'timestamp': DateTime.now().toIso8601String(),
'app_version': '1.0.0',
},
);
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
title: 'Lokotro Pay - ${_getPaymentMethodDisplayName(paymentMethod)}',
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.primary,
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}
class WalletPaymentOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const WalletPaymentOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<WalletPaymentOptionsScreen> createState() => _WalletPaymentOptionsScreenState();
}
class _WalletPaymentOptionsScreenState extends State<WalletPaymentOptionsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lokotro Wallet Options'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.primary.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.account_balance_wallet,
size: 48,
color: LokotroPayColors.primary,
),
),
const SizedBox(height: 16),
const Text(
'Lokotro Wallet Payment',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose how much information to provide',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
_buildOptionCard(
title: 'No User Info & No Wallet Info',
subtitle: 'Fill both personal and wallet information during payment',
icon: Icons.person_add,
color: LokotroPayColors.error,
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Wallet fields: Visible',
],
onTap: () => _startWalletPayment(
userInfo: 'none',
paymentMethodInfo: 'none',
),
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'No Wallet Info',
subtitle: 'Personal info provided, wallet info filled during payment',
icon: Icons.credit_card,
color: LokotroPayColors.warning,
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Wallet fields: Visible',
],
onTap: () => _startWalletPayment(
userInfo: 'full',
paymentMethodInfo: 'none',
),
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Wallet Info Provided',
subtitle: 'Both personal and wallet information pre-filled',
icon: Icons.check_circle,
color: LokotroPayColors.success,
details: [
'user_info: full',
'payment_method_info: full',
'Personal info form: Hidden',
'Wallet fields: Hidden',
],
onTap: () => _startWalletPayment(
userInfo: 'full',
paymentMethodInfo: 'full',
),
),
],
);
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required List<String> details,
required VoidCallback onTap,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
void _startWalletPayment({
required String userInfo,
required String paymentMethodInfo,
}) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configuration
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
acceptLanguage: 'en', // English for wallet payments
);
// Create payment body based on the selected option
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'wallet',
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information - only include if userInfo is 'full'
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// Wallet information - only include if paymentMethodInfo is 'full'
walletNumber: paymentMethodInfo == 'full' ? '3005710720108954' : null,
walletPin: paymentMethodInfo == 'full' ? '048698' : null,
// Merchant information
merchant: const LokotroMerchantInfo(
name: 'bilokos',
url: 'http://10.230.60.61:6495',
logo: 'http://10.230.60.61:6495/logo',
),
metadata: {
'source': 'flutter_example',
'payment_method': 'wallet',
'user_info': userInfo,
'payment_method_info': paymentMethodInfo,
'timestamp': DateTime.now().toIso8601String(),
'app_version': '1.0.0',
},
);
debugPrint("paymentBody walletNumber: ${paymentBody.walletNumber}");
debugPrint("paymentBody walletPin: ${paymentBody.walletPin}");
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.primary,
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}
// Flash Payment Options Screen (similar to Wallet)
class FlashPaymentOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const FlashPaymentOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<FlashPaymentOptionsScreen> createState() => _FlashPaymentOptionsScreenState();
}
class _FlashPaymentOptionsScreenState extends State<FlashPaymentOptionsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lokotro Flash Options'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.secondary.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.flash_on,
size: 48,
color: LokotroPayColors.secondary,
),
),
const SizedBox(height: 16),
const Text(
'Lokotro Flash Payment',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose how much information to provide',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
_buildOptionCard(
title: 'Both Forms Visible',
subtitle: 'You\'ll fill in personal info and flash details',
icon: Icons.edit,
color: LokotroPayColors.warning,
userInfo: 'none',
paymentMethodInfo: 'none',
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Flash form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Only Flash Form Visible',
subtitle: 'Personal info provided, enter flash details',
icon: Icons.flash_on,
color: LokotroPayColors.primary,
userInfo: 'full',
paymentMethodInfo: 'none',
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Flash form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Flash Info Provided',
subtitle: 'Flash details are pre-filled for you',
icon: Icons.check_circle,
color: LokotroPayColors.success,
userInfo: 'full',
paymentMethodInfo: 'full',
details: [
'user_info: full',
'payment_method_info: full',
'Personal info form: Hidden',
'Flash form: Hidden',
],
),
],
);
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required String userInfo,
required String paymentMethodInfo,
required List<String> details,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () => _startFlashPayment(userInfo, paymentMethodInfo),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
void _startFlashPayment(String userInfo, String paymentMethodInfo) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
acceptLanguage: 'fr', // French for flash payments (testing fallback)
);
// Create payment body based on the selected option (same as wallet)
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'flash',
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6496/notify/me',
successRedirectUrl: 'http://10.230.60.61:6496/notify/success',
failRedirectUrl: 'http://10.230.60.61:6496/notify/failed',
// User information (included when userInfo = 'full')
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// Flash information (included when paymentMethodInfo = 'full')
flashNumber: paymentMethodInfo == 'full' ? '0474892485' : null, // Your test flash number
flashPin: paymentMethodInfo == 'full' ? '3146' : null, // Your test flash PIN
);
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.secondary, // Use secondary color for flash
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}
// Mobile Money Payment Options Screen
class MobileMoneyPaymentOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const MobileMoneyPaymentOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<MobileMoneyPaymentOptionsScreen> createState() => _MobileMoneyPaymentOptionsScreenState();
}
class _MobileMoneyPaymentOptionsScreenState extends State<MobileMoneyPaymentOptionsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mobile Money Options'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.success.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.phone_android,
size: 48,
color: LokotroPayColors.success,
),
),
const SizedBox(height: 16),
const Text(
'Mobile Money Payment',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose how much information to provide',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
_buildOptionCard(
title: 'Both Forms Visible',
subtitle: 'You\'ll fill in personal info and phone number',
icon: Icons.edit,
color: LokotroPayColors.warning,
userInfo: 'none',
paymentMethodInfo: 'none',
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Phone form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Only Phone Form Visible',
subtitle: 'Personal info provided, enter phone number',
icon: Icons.phone_android,
color: LokotroPayColors.primary,
userInfo: 'full',
paymentMethodInfo: 'none',
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Phone form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Phone Info Provided',
subtitle: 'Phone number is pre-filled for you',
icon: Icons.check_circle,
color: LokotroPayColors.success,
userInfo: 'full',
paymentMethodInfo: 'full',
details: [
'user_info: full',
'payment_method_info: full',
'Personal info form: Hidden',
'Phone form: Hidden',
],
),
],
);
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required String userInfo,
required String paymentMethodInfo,
required List<String> details,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () => _startMobileMoneyPayment(userInfo, paymentMethodInfo),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
void _startMobileMoneyPayment(String userInfo, String paymentMethodInfo) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
acceptLanguage: 'en', // Spanish for mobile money (testing different language)
);
// Create payment body based on the selected option
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'mobile_money',
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information (included when userInfo = 'full')
firstName: userInfo == 'full' ? 'joseph' : null,
lastName: userInfo == 'full' ? 'kabatengo' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// Mobile money information (included when paymentMethodInfo = 'full')
mobileMoneyPhoneNumber: paymentMethodInfo == 'full' ? '2439978540000' : null,
);
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.success, // Use success color for mobile money
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}
// Card Payment Options Screen
class CardPaymentOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const CardPaymentOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<CardPaymentOptionsScreen> createState() => _CardPaymentOptionsScreenState();
}
class _CardPaymentOptionsScreenState extends State<CardPaymentOptionsScreen> {
String _selectedPaymentMethod = 'HOSTED_SESSION'; // Default to hosted session
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Payment Card Options'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.warning.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.credit_card,
size: 48,
color: LokotroPayColors.warning,
),
),
const SizedBox(height: 16),
const Text(
'Payment Card',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose how much information to provide',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
// Payment method selection
_buildPaymentMethodSelector(),
const SizedBox(height: 20),
// Show options based on selected payment method
..._buildPaymentOptions(),
],
);
}
Widget _buildPaymentMethodSelector() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.cardDark.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: LokotroPayColors.borderDark.withValues(alpha: 0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Payment Method',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildPaymentMethodOption(
'HOSTED_SESSION',
'Hosted Session',
'Secure session-based payment',
Icons.security,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildPaymentMethodOption(
'DIRECT_CAPTURE',
'Direct Capture',
'Direct card processing',
Icons.credit_card,
),
),
],
),
],
),
);
}
Widget _buildPaymentMethodOption(String value, String title, String subtitle, IconData icon) {
final isSelected = _selectedPaymentMethod == value;
return GestureDetector(
onTap: () {
setState(() {
_selectedPaymentMethod = value;
});
},
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected
? LokotroPayColors.primary.withValues(alpha: 0.2)
: LokotroPayColors.backgroundDark,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? LokotroPayColors.primary
: LokotroPayColors.borderDark.withValues(alpha: 0.3),
width: isSelected ? 2 : 1,
),
),
child: Column(
children: [
Icon(
icon,
color: isSelected
? LokotroPayColors.primary
: LokotroPayColors.textSecondaryDark,
size: 24,
),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: isSelected
? LokotroPayColors.primary
: LokotroPayColors.textPrimaryDark,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
List<Widget> _buildPaymentOptions() {
if (_selectedPaymentMethod == 'HOSTED_SESSION') {
return [
_buildOptionCard(
title: 'User Form Visible',
subtitle: 'You\'ll fill in personal info, card handled by secure session',
icon: Icons.person,
color: LokotroPayColors.primary,
userInfo: 'none',
paymentMethodInfo: 'full',
details: [
'user_info: none',
'payment_method_info: full',
'Personal info form: Visible',
'Card form: Secure Session',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'User Info Provided',
subtitle: 'Personal info pre-filled, card handled by secure session',
icon: Icons.check_circle,
color: LokotroPayColors.success,
userInfo: 'full',
paymentMethodInfo: 'full',
details: [
'user_info: full',
'payment_method_info: full',
'Personal info form: Hidden',
'Card form: Secure Session',
],
),
];
} else {
// DIRECT_CAPTURE options
return [
_buildOptionCard(
title: 'Both Forms Visible',
subtitle: 'You\'ll fill in personal info and card details',
icon: Icons.edit,
color: LokotroPayColors.warning,
userInfo: 'none',
paymentMethodInfo: 'none',
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Card form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Only Card Form Visible',
subtitle: 'Personal info provided, enter card details',
icon: Icons.credit_card,
color: LokotroPayColors.primary,
userInfo: 'full',
paymentMethodInfo: 'none',
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Card form: Visible',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Only User Form Visible',
subtitle: 'Card info provided, enter personal details',
icon: Icons.person,
color: LokotroPayColors.info,
userInfo: 'none',
paymentMethodInfo: 'full',
details: [
'user_info: none',
'payment_method_info: full',
'Personal info form: Visible',
'Card form: Hidden',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Card Info Provided',
subtitle: 'Card details are pre-filled for you',
icon: Icons.check_circle,
color: LokotroPayColors.success,
userInfo: 'full',
paymentMethodInfo: 'full',
details: [
'user_info: full',
'payment_method_info: full',
'Personal info form: Hidden',
'Card form: Hidden',
],
),
];
}
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required String userInfo,
required String paymentMethodInfo,
required List<String> details,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () => _startCardPayment(userInfo, paymentMethodInfo),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
void _startCardPayment(String userInfo, String paymentMethodInfo) async {
try {
// Check if using hosted session
if (_selectedPaymentMethod == 'HOSTED_SESSION') {
await _startHostedSessionPayment(userInfo: userInfo, paymentMethodInfo: paymentMethodInfo);
return;
}
// Continue with direct capture flow
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
acceptLanguage: 'de', // German for card payments (testing different language)
);
// Create payment body based on the selected option
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'card',
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information (included when userInfo = 'full')
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// Card information (only for DIRECT_CAPTURE when paymentMethodInfo = 'full')
// For HOSTED_SESSION, card details are NEVER passed (handled by secure session)
cardNumber: (_selectedPaymentMethod == 'DIRECT_CAPTURE' && paymentMethodInfo == 'full') ? '4035252004813539' : null,
cardExpiryDate: (_selectedPaymentMethod == 'DIRECT_CAPTURE' && paymentMethodInfo == 'full') ? '02/2027' : null,
cardCvv: (_selectedPaymentMethod == 'DIRECT_CAPTURE' && paymentMethodInfo == 'full') ? '127' : null,
cardHolderName: (_selectedPaymentMethod == 'DIRECT_CAPTURE' && paymentMethodInfo == 'full') ? 'joseph kabatengo' : null,
// Pass selected payment method to backend
mastercardPaymentMethod: _selectedPaymentMethod,
);
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.warning, // Use warning color for card
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
Future<void> _startHostedSessionPayment({
required String userInfo,
required String paymentMethodInfo,
}) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final paymentBody = LokotroPaymentBody(
customerReference: 'hosted_${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'card',
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information (provided when userInfo = 'full')
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// For HOSTED_SESSION, card details are NEVER passed (handled by secure session)
cardNumber: null,
cardExpiryDate: null,
cardCvv: null,
cardHolderName: null,
// Pass selected payment method to backend
mastercardPaymentMethod: _selectedPaymentMethod,
);
print('[Lokotro Pay] 🔐 Starting hosted session payment flow');
// Set up HTTP client
final httpClient = LokotroHttpClient.instance;
httpClient.setAppKey(widget.defaultAppKey);
// Step 1: Create payment and get session ID
final result = await httpClient.post<Map<String, dynamic>>(
'/payments/collect',
data: paymentBody.toJson(),
parser: (data) => data as Map<String, dynamic>,
);
if (result.isSuccess && result.data != null) {
final collectData = result.data!;
final transactionId = collectData['transaction_id'] as String?;
// Extract session_id and checkout_mode from additional_data
final additionalData = collectData['additional_data'] as Map<String, dynamic>?;
final sessionId = additionalData?['session_id'] as String?;
final checkoutMode = additionalData?['checkout_mode'] as String?;
if (sessionId != null) {
print('[Lokotro Pay] 🔐 Session created: $sessionId');
print('[Lokotro Pay] 🔐 Checkout mode: $checkoutMode');
// Step 2: Show Mastercard website checkout form
if (mounted) {
final paymentResult = await Navigator.of(context).push<Map<String, dynamic>>(
MaterialPageRoute(
builder: (context) => MastercardHostedSessionWidget(
sessionId: sessionId,
merchantId: 'MMSG', // Your merchant ID
gatewayUrl: 'https://ap-gateway.mastercard.com',
apiVersion: '74',
// Note: HOSTED_CHECKOUT mode provides better UX than PAYMENT_LINK
onPaymentComplete: (success, result) {
Navigator.of(context).pop(result);
},
),
),
);
if (paymentResult != null && paymentResult['success'] == true) {
print('[Lokotro Pay] 🔐 Card data captured, proceeding with authorization');
// Step 3: Submit payment with updated session and card data
await _submitHostedSessionPayment(
transactionId!,
sessionId,
paymentResult,
);
} else {
print('[Lokotro Pay] ❌ Card data capture failed or cancelled');
_showPaymentResult(false, 'Payment cancelled or failed to capture card data');
}
}
} else {
print('[Lokotro Pay] ❌ No session ID received');
_showPaymentResult(false, 'Failed to create payment session');
}
} else {
print('[Lokotro Pay] ❌ Payment creation failed: ${result.message}');
_showPaymentResult(false, result.message ?? 'Payment creation failed');
}
} catch (e) {
print('[Lokotro Pay] ❌ Hosted session error: $e');
_showPaymentResult(false, 'Payment error: $e');
}
}
void _showPaymentResult(bool success, String message) {
if (mounted) {
// Use proper Lokotro Pay result screens instead of simple dialogs
if (success) {
LokotroPayUtils.showSnackBar(
context,
message,
type: LokotroPayResultScreen.successScreen,
);
} else {
LokotroPayUtils.showSnackBar(
context,
message,
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
Future<void> _submitHostedSessionPayment(
String transactionId,
String sessionId,
Map<String, dynamic> cardData,
) async {
try {
// Set up HTTP client
final httpClient = LokotroHttpClient.instance;
httpClient.setAppKey(widget.defaultAppKey);
// First, get transaction details to get the correct payment_method_id
print('[Lokotro Pay] 🔍 Getting transaction details for payment_method_id');
final detailsResult = await httpClient.get<Map<String, dynamic>>(
'/payments/transaction/$transactionId',
parser: (data) => data as Map<String, dynamic>,
);
String? paymentMethodId;
if (detailsResult.isSuccess && detailsResult.data != null) {
// The payment_method_id is in the 'data' object
final responseData = detailsResult.data!['data'] as Map<String, dynamic>?;
paymentMethodId = responseData?['payment_method_id'] as String?;
print('[Lokotro Pay] 🔍 Found payment_method_id: $paymentMethodId');
}
if (paymentMethodId == null) {
throw Exception('Could not get payment_method_id from transaction details');
}
// Submit payment with session ID and card data
final submitData = {
'payment_id': transactionId,
'payment_method_id': paymentMethodId, // Use the actual MongoDB ObjectId
'session_id': sessionId,
'card_data': cardData, // This would be encrypted in a real implementation
'mastercard_payment_method': 'HOSTED_SESSION', // Ensure backend uses hosted session
};
final submitResult = await httpClient.post<Map<String, dynamic>>(
'/payments/submit',
data: submitData,
parser: (data) => data as Map<String, dynamic>,
);
if (submitResult.isSuccess && submitResult.data != null) {
// Handle nested response structure
final responseData = submitResult.data!['data'] as Map<String, dynamic>? ?? submitResult.data!;
final status = responseData['status'] as String? ?? '';
print('[Lokotro Pay] 🔍 Payment submission response status: $status');
if (status == 'completed') {
// Payment successfully completed
_showPaymentResult(true, 'Payment completed successfully!');
} else if (status == 'failed') {
// Payment failed
_showPaymentResult(false, responseData['message'] as String? ?? 'Payment failed');
} else if (status == 'processing' || status == 'session_created') {
// Payment is being processed - show progress and poll for result
print('[Lokotro Pay] 🔄 Payment processing, starting status polling...');
await _showProgressAndPollStatus(transactionId);
} else {
// Unknown status
print('[Lokotro Pay] ❌ Unknown payment status: $status');
print('[Lokotro Pay] 📄 Full response: $responseData');
_showPaymentResult(false, 'Unknown payment status: $status');
}
} else {
_showPaymentResult(false, submitResult.message ?? 'Payment submission failed');
}
} catch (e) {
print('[Lokotro Pay] ❌ Hosted session submission error: $e');
_showPaymentResult(false, 'Payment submission error: $e');
}
}
Future<void> _showProgressAndPollStatus(String transactionId) async {
if (!mounted) return;
// Show progress screen
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
const Text('Processing Payment...'),
const SizedBox(height: 8),
const Text(
'Please wait while we process your payment with Mastercard.',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
);
try {
// Set up HTTP client
final httpClient = LokotroHttpClient.instance;
httpClient.setAppKey(widget.defaultAppKey);
// Poll for payment status
const maxAttempts = 30; // 30 attempts = 1 minute
const pollInterval = Duration(seconds: 2);
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
print('[Lokotro Pay] 🔄 Polling attempt $attempt/$maxAttempts');
await Future.delayed(pollInterval);
try {
// Use the new Mastercard status endpoint for hosted session payments
final statusResult = await httpClient.get<Map<String, dynamic>>(
'/payments/mastercard/status/$transactionId',
parser: (data) => data as Map<String, dynamic>,
);
if (statusResult.isSuccess && statusResult.data != null) {
final responseData = statusResult.data!['data'] as Map<String, dynamic>?;
final status = responseData?['status'] as String? ?? '';
final mastercardResult = responseData?['mastercard_result'] as String? ?? '';
final mastercardStatus = responseData?['mastercard_status'] as String? ?? '';
print('[Lokotro Pay] 🔄 Current status: $status');
print('[Lokotro Pay] 🔄 Mastercard result: $mastercardResult, status: $mastercardStatus');
if (status == 'completed') {
// Payment completed successfully - money was captured by Mastercard
if (mounted) {
Navigator.of(context).pop(); // Close progress dialog
_showPaymentResult(true, 'Payment completed successfully! Money captured by Mastercard.');
}
return;
} else if (status == 'failed') {
// Payment failed at Mastercard
if (mounted) {
Navigator.of(context).pop(); // Close progress dialog
_showPaymentResult(false, responseData?['message'] as String? ?? 'Payment failed at Mastercard');
}
return;
} else if (status == 'error') {
// Error checking status
if (mounted) {
Navigator.of(context).pop(); // Close progress dialog
_showPaymentResult(false, responseData?['message'] as String? ?? 'Error checking payment status');
}
return;
}
// Continue polling for 'processing' status
}
} catch (e) {
print('[Lokotro Pay] ⚠️ Polling error (attempt $attempt): $e');
// Continue polling on error
}
}
// Timeout reached
if (mounted) {
Navigator.of(context).pop(); // Close progress dialog
_showPaymentResult(false, 'Payment processing timeout. Please check your payment status.');
}
} catch (e) {
if (mounted) {
Navigator.of(context).pop(); // Close progress dialog
_showPaymentResult(false, 'Error monitoring payment status: $e');
}
}
}
}
// Payment Method Selection Options Screen
class AllPaymentMethodsOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const AllPaymentMethodsOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<AllPaymentMethodsOptionsScreen> createState() => _AllPaymentMethodsOptionsScreenState();
}
class _AllPaymentMethodsOptionsScreenState extends State<AllPaymentMethodsOptionsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select Payment Method'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: LokotroPayColors.primary.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.payment,
size: 48,
color: LokotroPayColors.primary,
),
),
const SizedBox(height: 16),
const Text(
'Select Payment Method',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Choose from available payment methods with fee calculation',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
_buildOptionCard(
title: 'Show Payment Method Selection',
subtitle: 'User will choose from available payment methods',
icon: Icons.list,
color: LokotroPayColors.primary,
userInfo: 'none',
paymentMethodInfo: 'none',
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Payment method: User selects',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Personal Info Provided',
subtitle: 'Personal info filled, user selects payment method',
icon: Icons.person,
color: LokotroPayColors.success,
userInfo: 'full',
paymentMethodInfo: 'none',
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Payment method: User selects',
],
),
],
);
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required String userInfo,
required String paymentMethodInfo,
required List<String> details,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () => _startAllPaymentMethodsFlow(userInfo, paymentMethodInfo),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
void _startAllPaymentMethodsFlow(String userInfo, String paymentMethodInfo) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
// acceptLanguage defaults to 'fr' (French)
);
// Create payment body with 'none' payment method
final paymentBody = LokotroPaymentBody(
customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
amount: '1.00',
currency: 'usd',
paymentMethod: 'none', // This will trigger payment method selection
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
deliveryBehaviour: 'direct_delivery',
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/notify/success',
failRedirectUrl: 'http://10.230.60.61:6495/notify/failed',
// User information (included when userInfo = 'full')
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
);
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: LokotroPayColors.primary, // Use primary color for all methods
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
// Handle initialization error
if (mounted) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.failedToInitializePayment(e.toString()),
type: LokotroPayResultScreen.errorScreen,
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
// Check payment status to show appropriate message
switch (response.paymentStatus) {
case LokotroPaymentStatus.approved:
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, response.amount.toStringAsFixed(2)),
type: LokotroPayResultScreen.successScreen,
);
break;
case LokotroPaymentStatus.processing:
case LokotroPaymentStatus.pending:
LokotroPayUtils.showSnackBar(
context,
'${l.paymentProcessing}. ${response.message}',
type: LokotroPayResultScreen.infoScreen,
);
break;
default:
LokotroPayUtils.showSnackBar(
context,
response.message.isNotEmpty ? response.message : l.paymentResponseReceived,
type: LokotroPayResultScreen.successScreen,
);
}
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}
// Bank Transfer Options Screen
class BankTransferOptionsScreen extends StatefulWidget {
final bool isProduction;
final String defaultAppKey;
const BankTransferOptionsScreen({
super.key,
required this.isProduction,
required this.defaultAppKey,
});
@override
State<BankTransferOptionsScreen> createState() => _BankTransferOptionsScreenState();
}
class _BankTransferOptionsScreenState extends State<BankTransferOptionsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bank Transfer'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(),
const SizedBox(height: 32),
_buildOptionsGrid(),
],
),
),
);
}
Widget _buildHeader() {
return Card(
color: LokotroPayColors.cardDark,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.indigo.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.account_balance,
size: 48,
color: Colors.indigo,
),
),
const SizedBox(height: 16),
const Text(
'Bank Transfer',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 8),
Text(
'Secure bank transfer payment with manual validation',
style: TextStyle(
fontSize: 16,
color: LokotroPayColors.textSecondaryDark,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOptionsGrid() {
return Column(
children: [
_buildOptionCard(
title: 'Bank Transfer (Hold Delivery)',
subtitle: 'Payment held until bank validation',
icon: Icons.account_balance,
color: Colors.indigo,
userInfo: 'full',
paymentMethodInfo: 'none',
details: [
'user_info: full',
'payment_method_info: none',
'Personal info form: Hidden',
'Delivery: Hold until validated',
],
),
const SizedBox(height: 16),
_buildOptionCard(
title: 'Bank Transfer (No User Info)',
subtitle: 'Minimal information required',
icon: Icons.account_balance_outlined,
color: Colors.indigo.shade300,
userInfo: 'none',
paymentMethodInfo: 'none',
details: [
'user_info: none',
'payment_method_info: none',
'Personal info form: Visible',
'Delivery: Hold until validated',
],
),
],
);
}
Widget _buildOptionCard({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required String userInfo,
required String paymentMethodInfo,
required List<String> details,
}) {
return Card(
color: LokotroPayColors.cardDark,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () => _initiateBankTransfer(
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 24,
color: color,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: LokotroPayColors.textPrimaryDark,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontSize: 14,
color: LokotroPayColors.textSecondaryDark,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: LokotroPayColors.backgroundDark.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details.map((detail) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
const Icon(
Icons.arrow_right,
size: 16,
color: LokotroPayColors.secondary,
),
const SizedBox(width: 8),
Text(
detail,
style: TextStyle(
fontSize: 13,
color: LokotroPayColors.textSecondaryDark,
fontFamily: 'monospace',
),
),
],
),
)).toList(),
),
),
],
),
),
),
);
}
Future<void> _initiateBankTransfer({
required String userInfo,
required String paymentMethodInfo,
}) async {
try {
// Initialize environment first
await LokotroPayEnv.initialize(isProduction: widget.isProduction);
// Create payment configurations
final configs = LokotroPayConfigs(
token: widget.defaultAppKey,
isProduction: widget.isProduction,
acceptLanguage: 'fr', // Bank transfers typically in English
);
// Create payment body with 'bank_transfer' payment method
final paymentBody = LokotroPaymentBody(
customerReference: 'bank${DateTime.now().millisecondsSinceEpoch}',
amount: '100.00', // Higher amount for bank transfers
currency: 'usd',
paymentMethod: 'bank_transfer', // Bank transfer payment method
userInfo: userInfo,
paymentMethodInfo: paymentMethodInfo,
feeCoveredBy: 'buyer',
// User information (included when userInfo = 'full')
firstName: userInfo == 'full' ? 'josmid' : null,
lastName: userInfo == 'full' ? 'joes' : null,
phoneNumber: userInfo == 'full' ? '2439978540000' : null,
email: userInfo == 'full' ? '[email protected]' : null,
// Bank transfer information (included when paymentMethodInfo = 'full')
deliveryBehaviour: 'hold_delivery', // Automatically set for bank transfers
notifyUrl: 'http://10.230.60.61:6495/notify/me',
successRedirectUrl: 'http://10.230.60.61:6495/success',
failRedirectUrl: 'http://10.230.60.61:6495/failed',
merchant: const LokotroMerchantInfo(
name: 'Bank Transfer Test',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
),
);
// // Create payment body based on the selected option (same as wallet)
// final paymentBody = LokotroPaymentBody(
// customerReference: '0wru${DateTime.now().millisecondsSinceEpoch}',
// amount: '1.00',
// currency: 'usd',
// paymentMethod: 'flash',
// userInfo: userInfo,
// paymentMethodInfo: paymentMethodInfo,
// feeCoveredBy: 'buyer',
// deliveryBehaviour: 'direct_delivery',
// notifyUrl: 'http://10.230.60.61:6496/notify/me',
// successRedirectUrl: 'http://10.230.60.61:6496/notify/success',
// failRedirectUrl: 'http://10.230.60.61:6496/notify/failed',
// // User information (included when userInfo = 'full')
// firstName: userInfo == 'full' ? 'josmid' : null,
// lastName: userInfo == 'full' ? 'joes' : null,
// phoneNumber: userInfo == 'full' ? '2439978540000' : null,
// email: userInfo == 'full' ? '[email protected]' : null,
// // Flash information (included when paymentMethodInfo = 'full')
// flashNumber: paymentMethodInfo == 'full' ? '0474892485' : null, // Your test flash number
// flashPin: paymentMethodInfo == 'full' ? '3146' : null, // Your test flash PIN
// );
// Navigate to payment screen
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LokotroPayCheckout(
// title is now automatically localized based on payment method
configs: configs,
paymentBody: paymentBody,
onResponse: _handlePaymentResponse,
onError: _handlePaymentError,
backgroundColor: LokotroPayColors.backgroundDark,
themeConfig: LokotroPayThemeConfig.dark(
primaryColor: Colors.indigo,
backgroundColor: LokotroPayColors.backgroundDark,
),
),
),
);
}
} catch (e) {
if (mounted) {
final l = LokotroPayLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l.failedToInitializePayment(e.toString())),
backgroundColor: LokotroPayColors.error,
),
);
}
}
}
void _handlePaymentResponse(LokotroPayOnResponse response) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentSuccessfulAmount(response.currency, LokotroPayUtils.formatCurrency(response.amount, response.currency)),
type: LokotroPayResultScreen.successScreen,
);
}
void _handlePaymentError(LokotroPayOnError error) {
final l = LokotroPayLocalizations.of(context);
LokotroPayUtils.showSnackBar(
context,
l.paymentFailedWithMessage(error.message),
type: LokotroPayResultScreen.errorScreen,
);
}
}