flick_payment_sdk 1.0.1
flick_payment_sdk: ^1.0.1 copied to clipboard
A Flutter SDK for Flick Payment processing
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flick_payment_sdk/flick_payment_sdk.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Simple Payment Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AmountInputPage(),
);
}
}
// First page where user inputs amount
class AmountInputPage extends StatefulWidget {
const AmountInputPage({super.key});
@override
State<AmountInputPage> createState() => _AmountInputPageState();
}
class _AmountInputPageState extends State<AmountInputPage> {
final TextEditingController _amountController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Enter Payment Amount'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount (£)',
hintText: 'e.g. 5.00 (minimum £5)',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an amount';
}
final amount = double.tryParse(value);
if (amount == null) {
return 'Please enter a valid number';
}
if (amount <= 0) return 'Amount must be positive';
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Valid amount entered - navigate to payment page
final userAmount = double.parse(_amountController.text);
final amountInPence = (userAmount * 100).toInt();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PaymentPage(
userAmount: userAmount,
backendAmount: amountInPence.toString(),
),
),
);
}
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 15,
),
),
child: const Text('Proceed to Payment'),
),
],
),
),
),
);
}
}
// Payment page that uses the FlickPayment SDK
class PaymentPage extends StatefulWidget {
final double userAmount; // What user sees (e.g. 5.0)
final String backendAmount; // What gets sent to backend (e.g. "500")
const PaymentPage({
super.key,
required this.userAmount,
required this.backendAmount,
});
@override
State<PaymentPage> createState() => _PaymentPageState();
}
class _PaymentPageState extends State<PaymentPage> {
final FlickPayment _flickPayment = FlickPayment();
@override
void initState() {
super.initState();
_initializePayment();
}
Future<void> _initializePayment() async {
final success = await _flickPayment.initialize(
PaymentConfig(
customerEmail: 'user@example.com',
amount: widget.backendAmount,
currency: 'GBP',
transactionId: 'Flick-${DateTime.now().millisecondsSinceEpoch}',
bearerToken: 'your-api-bearer-token',
redirectUrl: "https://merchant.getflick.co/"),
onPaymentCompleted: _showPaymentResult,
);
if (!mounted) return;
setState(() {});
if (!success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
_flickPayment.initializationError ??
'Payment initialization failed',
),
duration: const Duration(seconds: 5),
),
);
}
}
// Add this reset function
Future<void> _resetApp() async {
// First reset the SDK
await _flickPayment.reset();
// Then navigate back to the amount input page
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const AmountInputPage(),
),
);
}
void _showPaymentResult(PaymentStatusDetails statusDetails) {
if (statusDetails.transactionStatus.toLowerCase() == 'successful') {
_showSuccessDialog(
'Payment Successful',
'Amount: £${(int.parse(statusDetails.transactionAmount)).toStringAsFixed(2)}\n'
'Reference: ${statusDetails.transactionRef}',
);
} else {
_showErrorDialog(
'Payment ${statusDetails.transactionStatus}',
statusDetails.description,
);
}
}
void _showSuccessDialog(String title, String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
const Icon(Icons.check_circle, color: Colors.green),
const SizedBox(width: 8),
Text(title),
],
),
content: Text(message),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: const Text('Done'),
),
],
),
);
}
void _showErrorDialog(String title, String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
const Icon(Icons.error, color: Colors.red),
const SizedBox(width: 8),
Text(title),
],
),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Try Again'),
),
TextButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: const Text('Cancel'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Complete Payment'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Start over',
onPressed: _resetApp,
),
],
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// Payment amount display at the top
Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
const Text(
'Amount to Pay',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 8),
Text(
'£${widget.userAmount.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(height: 20),
Expanded(
child: Container(),
),
// Payment button at the very bottom
_flickPayment.createPaymentButton(context),
],
),
),
);
}
}