end2endpay_flutter_sdk 1.0.3
end2endpay_flutter_sdk: ^1.0.3 copied to clipboard
A Flutter SDK for End2EndPay payment integration with secure WebView support.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:end2endpay_flutter_sdk/end2endpay_flutter_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'End2EndPay Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _formKey = GlobalKey<FormState>();
final _amountController = TextEditingController(text: '100.00');
final _currencyController = TextEditingController(text: 'NGN');
final _emailController = TextEditingController(text: 'customer@example.com');
final _apiKeyController = TextEditingController(text: '<api key>');
final _accessTokenController = TextEditingController(text: '<accessToken>');
@override
void dispose() {
_amountController.dispose();
_currencyController.dispose();
_emailController.dispose();
_apiKeyController.dispose();
_accessTokenController.dispose();
super.dispose();
}
void _makePayment() {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => PaymentScreen(
apiKey: _apiKeyController.text,
accessToken: _accessTokenController.text,
amount: double.parse(_amountController.text),
currency: _currencyController.text,
email: _emailController.text,
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('End2EndPay Demo'), elevation: 2),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
const Icon(Icons.payment, size: 80, color: Colors.blue),
const SizedBox(height: 20),
const Text(
'Payment Configuration',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
TextFormField(
controller: _apiKeyController,
decoration: const InputDecoration(
labelText: 'API Key',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.key),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter API key';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _accessTokenController,
decoration: const InputDecoration(
labelText: 'Access Token',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter access token';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter amount';
}
final amount = double.tryParse(value);
if (amount == null || amount <= 0) {
return 'Please enter valid amount';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _currencyController,
decoration: const InputDecoration(
labelText: 'Currency',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.currency_exchange),
hintText: 'USD, EUR, GBP',
),
maxLength: 3,
textCapitalization: TextCapitalization.characters,
validator: (value) {
if (value == null || value.length != 3) {
return 'Please enter 3-letter currency code';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Payer Email',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter email';
}
if (!RegExp(
r'^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$',
).hasMatch(value)) {
return 'Please enter valid email';
}
return null;
},
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _makePayment,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
textStyle: const TextStyle(fontSize: 18),
),
child: const Text('Proceed to Payment'),
),
const SizedBox(height: 20),
const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Note:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 8),
Text(
'• Replace API Key and Access Token with your actual credentials\n'
'• All fields are required\n'
'• Amount must be greater than 0\n'
'• Currency must be a valid 3-letter code',
style: TextStyle(fontSize: 14),
),
],
),
),
),
],
),
),
),
);
}
}
class PaymentScreen extends StatelessWidget {
final String apiKey;
final String accessToken;
final double amount;
final String currency;
final String email;
const PaymentScreen({
super.key,
required this.apiKey,
required this.accessToken,
required this.amount,
required this.currency,
required this.email,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Complete Payment'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
),
body: End2EndPayWebView(
apiKey: apiKey,
accessToken: accessToken,
amount: amount,
currency: currency,
reference: 'ORDER-${DateTime.now().millisecondsSinceEpoch}',
payerEmail: email,
onSuccess: (response) {
debugPrint('✅ Payment Success: $response');
// Try to parse the response
String displayMessage = response;
try {
final data = jsonDecode(response);
displayMessage =
'Transaction ID: ${data['transactionId'] ?? 'N/A'}\n'
'Status: ${data['status'] ?? 'Success'}';
} catch (e) {
// If parsing fails, use the raw response
}
// Navigate to success screen
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder:
(context) => SuccessScreen(
message: displayMessage,
amount: amount,
currency: currency,
),
),
);
},
onError: (error) {
debugPrint('❌ Payment Error: $error');
// Show error dialog
showDialog(
context: context,
barrierDismissible: false,
builder:
(ctx) => AlertDialog(
title: const Row(
children: [
Icon(Icons.error, color: Colors.red),
SizedBox(width: 8),
Text('Payment Failed'),
],
),
content: Text(error),
actions: [
TextButton(
onPressed: () {
Navigator.pop(ctx);
Navigator.pop(context);
},
child: const Text('Close'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(ctx);
// The WebView will remain and can retry
},
child: const Text('Retry'),
),
],
),
);
},
onCancel: (message) {
debugPrint('⚠️ Payment Cancelled: $message');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Payment was cancelled'),
action: SnackBarAction(
label: 'Retry',
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => PaymentScreen(
apiKey: apiKey,
accessToken: accessToken,
amount: amount,
currency: currency,
email: email,
),
),
);
},
),
),
);
},
),
);
}
}
class SuccessScreen extends StatelessWidget {
final String message;
final double amount;
final String currency;
const SuccessScreen({
super.key,
required this.message,
required this.amount,
required this.currency,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Payment Complete'),
automaticallyImplyLeading: false,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle, color: Colors.green, size: 100),
const SizedBox(height: 24),
const Text(
'Payment Successful!',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Text(
'$currency ${amount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 24),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Transaction Details',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(message, style: const TextStyle(fontSize: 14)),
],
),
),
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
icon: const Icon(Icons.home),
label: const Text('Back to Home'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
textStyle: const TextStyle(fontSize: 18),
),
),
],
),
),
),
);
}
}