payfast_flutter 0.0.4
payfast_flutter: ^0.0.4 copied to clipboard
Flutter SDK for PayFast payment gateway integration with Android, iOS, and Web support.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:payfast_flutter/payfast_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
static final Uri _webTokenServerUri = Uri.parse(
'http://localhost:8080/api/payfast/access-token',
);
PayFastResult? _lastResult;
String _paymentStatus = 'Ready to pay';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('PayFast SDK Demo'),
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Text(
'Payment Status',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
Text(
_paymentStatus,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
if (_lastResult != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _lastResult!.success
? Colors.green[100]
: Colors.red[100],
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _lastResult!.success
? Colors.green
: Colors.red,
),
),
child: Column(
children: [
Text(
_lastResult!.success
? '✓ Payment Successful'
: '✗ Payment Failed',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _lastResult!.success
? Colors.green[700]
: Colors.red[700],
),
),
const SizedBox(height: 8),
Text(
_lastResult!.message,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 14),
),
if (_lastResult!.transactionId != null) ...[
const SizedBox(height: 8),
Text(
'Transaction ID: ${_lastResult!.transactionId}',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
],
),
),
],
],
),
),
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: () => _processPayment(
context,
amount: '10.00',
description: 'Premium Package',
),
icon: const Icon(Icons.payment),
label: const Text('Pay 10 PKR'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () => _processPayment(
context,
amount: '500.00',
description: 'Deluxe Package',
),
icon: const Icon(Icons.payment),
label: const Text('Pay 500 PKR'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () => _processPayment(
context,
amount: '1000.00',
description: 'Enterprise Package',
),
icon: const Icon(Icons.payment),
label: const Text('Pay 1000 PKR'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
const SizedBox(height: 32),
Card(
color: Colors.blue[50],
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Demo Credentials',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
Text(
kIsWeb
? 'Merchant ID: YOUR_MERCHANT_ID\n'
'Mode: Sandbox\n'
'Web: run `dart run tool/payfast_token_server.dart` first.\n'
'Web: success/failure URLs must use the same origin as this app.\n'
'Token Proxy: $_webTokenServerUri\n'
'Replace the placeholders below with your own credentials.'
: 'Merchant ID: YOUR_MERCHANT_ID\n'
'Mode: Sandbox\n'
'Replace the placeholders below with your own credentials.',
style: const TextStyle(fontSize: 12),
),
],
),
),
),
],
),
),
);
}
Future<String?> _fetchWebAccessToken(PayFastConfig config) async {
try {
final response = await http.post(
_webTokenServerUri,
headers: const {
'Content-Type': 'application/json',
},
body: jsonEncode({
'merchantId': config.merchantId,
'securedKey': config.securedKey,
'basketId': config.basketId,
'amount': config.amount,
'currencyCode': config.currency,
}),
);
if (response.statusCode != 200) {
debugPrint(
'Web token proxy failed: ${response.statusCode} ${response.body}',
);
return null;
}
final data = jsonDecode(response.body);
if (data is! Map<String, dynamic>) {
return null;
}
final token = data['accessToken'] ?? data['ACCESS_TOKEN'];
return token is String && token.trim().isNotEmpty ? token.trim() : null;
} catch (e) {
debugPrint('Web token proxy error: $e');
return null;
}
}
void _processPayment(
BuildContext context, {
required String amount,
required String description,
}) {
if (!context.mounted) {
return;
}
final successUrl = kIsWeb
? Uri.base.resolve('/payfast-success').toString()
: 'https://example.com/success';
final failureUrl = kIsWeb
? Uri.base.resolve('/payfast-failure').toString()
: 'https://example.com/failure';
final checkoutUrl = kIsWeb
? Uri.base.resolve('/payfast-ipn').toString()
: 'https://example.com/ipn';
final config = PayFastConfig(
merchantId: '241665',
securedKey: 'tDy0TwuynmeiJP3FiVg-YAOC',
basketId: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
amount: amount,
currency: 'PKR',
txnDesc: description,
customerEmail: 'customer@example.com',
customerMobile: '03001234567',
successUrl: successUrl,
failureUrl: failureUrl,
checkoutUrl: checkoutUrl,
environment: 'sandbox',
);
setState(() {
_paymentStatus = kIsWeb
? 'Requesting secure web access token for $amount PKR...'
: 'Processing payment for $amount PKR...';
});
PayFast.startPayment(
context: context,
config: config,
accessTokenProvider: kIsWeb ? _fetchWebAccessToken : null,
onResult: (result) {
if (!mounted) {
return;
}
setState(() {
_lastResult = result;
_paymentStatus =
result.success ? 'Payment Completed' : 'Payment Failed';
});
debugPrint('Payment Result: ${result.success}');
debugPrint('Message: ${result.message}');
debugPrint('Transaction ID: ${result.transactionId}');
},
);
}
}