flutter_payment_plugin 1.0.0
flutter_payment_plugin: ^1.0.0 copied to clipboard
Flutter Payment Plugin: simple payment initiation with WebView and return URL handling.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math';
import 'dart:async';
import 'dart:convert';
import 'package:flutter_payment_plugin/flutter_payment_plugin.dart';
import 'hash_generator.dart';
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _plugin = FlutterPaymentPlugin();
final _hasher = HashGenerator();
StreamSubscription<String>? _logSub;
// ====== Defaults (prefilled) ======
final _baseUrlCtl = TextEditingController(text: 'https://pgbiz.omniware.in');
// Merchant creds
final _apiKeyCtl = TextEditingController(text: 'fb6bca86-b429-4abf-a42f-824bdd29022e');
final _saltCtl = TextEditingController(text: '80c67bfdf027da08de88ab5ba903fecafaab8f6d');
// Payment fields (user inputs)
final _orderIdCtl = TextEditingController();
final _amountCtl = TextEditingController(text: '2');
final _nameCtl = TextEditingController(text: 'Tester Sharma');
final _phoneCtl = TextEditingController(text: '9876543210');
final _emailCtl = TextEditingController(text: 'tester@example.com');
final _returnUrlCtl = TextEditingController(text: 'https://pgbiz.omniware.in/tnpv2/return_page.php');
final _descCtl = TextEditingController(text: 'Payment Short Description');
final _currencyCtl = TextEditingController(text: 'INR');
final _countryCtl = TextEditingController(text: 'IND');
final _cityCtl = TextEditingController(text: 'Bangalore');
final _stateCtl = TextEditingController(text: 'Karnataka');
final _addr1Ctl = TextEditingController(text: 'ad1');
final _addr2Ctl = TextEditingController(text: 'ad2');
final _zipCtl = TextEditingController(text: '560001');
final _enableAutoRefundCtl = TextEditingController(text: 'n');
String _mode = 'LIVE'; // or TEST
String _hash = '';
String _lastStatusTitle = '';
String _lastResponseJson = '';
@override
void initState() {
super.initState();
_initPlatformVersion();
_orderIdCtl.text = _generateOrderId();
_logSub = FlutterPaymentPlugin.logLines.listen((s) { debugPrint(s); });
}
Future<void> _initPlatformVersion() async {
String pv;
try {
pv = await _plugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
pv = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() => _platformVersion = pv);
}
@override
void dispose() {
_logSub?.cancel();
super.dispose();
}
String _generateOrderId() {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numbers = '0123456789';
final r = Random();
final letter = letters[r.nextInt(letters.length)];
final digits = List.generate(3, (_) => numbers[r.nextInt(numbers.length)]).join();
return '$letter$digits';
}
void _regenOrderId() {
setState(() {
_orderIdCtl.text = _generateOrderId();
});
}
void _generateHash() {
// CamelCase inputs for hashing (MUST match Swift keys)
final camel = <String, String>{
'mode': _mode,
'amount': _amountCtl.text,
'name': _nameCtl.text,
'phone': _phoneCtl.text,
'email': _emailCtl.text,
'returnURL': _returnUrlCtl.text,
'description': _descCtl.text,
'currency': _currencyCtl.text,
'country': _countryCtl.text,
'city': _cityCtl.text,
'state': _stateCtl.text,
'addressLine1': _addr1Ctl.text,
'addressLine2': _addr2Ctl.text,
'zipCode': _zipCtl.text,
'enable_auto_refund': _enableAutoRefundCtl.text,
'orderID': _orderIdCtl.text,
};
final hash = _hasher.generateHash(
salt: _saltCtl.text.trim(),
apiKey: _apiKeyCtl.text.trim(),
camelCaseInputs: camel,
);
setState(() => _hash = hash);
}
Future<void> _startPayment() async {
// Basic field checks
if (_baseUrlCtl.text.trim().isEmpty ||
_apiKeyCtl.text.trim().isEmpty ||
_saltCtl.text.trim().isEmpty) {
_toast('Base URL, API Key and SALT are required');
return;
}
if (_hash.isEmpty) {
_generateHash(); // auto-generate if user forgot
if (_hash.isEmpty) {
_toast('Could not generate hash'); return;
}
}
// Snake_case params for POST form
final params = <String, String>{
'api_key': _apiKeyCtl.text.trim(),
'order_id': _orderIdCtl.text.trim(),
'salt': _saltCtl.text.trim(),
'hash': _hash,
'mode': _mode,
'amount': _amountCtl.text.trim(),
'name': _nameCtl.text.trim(),
'phone': _phoneCtl.text.trim(),
'email': _emailCtl.text.trim(),
'return_url': _returnUrlCtl.text.trim(),
'description': _descCtl.text.trim(),
'currency': _currencyCtl.text.trim(),
'country': _countryCtl.text.trim(),
'city': _cityCtl.text.trim(),
'state': _stateCtl.text.trim(),
'address_line_1': _addr1Ctl.text.trim(),
'address_line_2': _addr2Ctl.text.trim(),
'zip_code': _zipCtl.text.trim(),
'udf1': '',
'udf2': '',
'udf3': '',
'udf4': '',
'udf5': '',
'enable_auto_refund': _enableAutoRefundCtl.text.trim(),
};
try {
final result = await FlutterPaymentPlugin.openPayment(
url: _baseUrlCtl.text.trim(),
params: params,
title: 'Payment',
javaScript: true,
actionPath: 'v2/paymentrequest',
);
final message = result.cancelled
? 'Payment cancelled'
: (result.success ? 'Payment success' : 'Payment finished with response');
final pretty = _prettyJson(result.data);
if (!mounted) return;
setState(() {
_lastStatusTitle = message;
_lastResponseJson = pretty;
});
} on PlatformException catch (e) {
if (!mounted) return;
_toast('Failed to open Payment WebView: ${e.message}');
}
}
String _prettyJson(String? s) {
if (s == null || s.isEmpty) return '';
try {
final obj = jsonDecode(s);
return const JsonEncoder.withIndent(' ').convert(obj);
} catch (_) {
return s;
}
}
void _toast(String msg) {
// Use the global key, not ScaffoldMessenger.of(context)
final sm = scaffoldMessengerKey.currentState;
sm?.clearSnackBars();
sm?.showSnackBar(SnackBar(content: Text(msg)));
}
Widget _field(String label, TextEditingController ctl, {TextInputType? type, bool readOnly = false}) {
return TextField(
controller: ctl,
readOnly: readOnly,
keyboardType: type,
decoration: InputDecoration(labelText: label, border: const OutlineInputBorder()),
);
}
@override
Widget build(BuildContext context) {
final pad = const SizedBox(height: 12);
return MaterialApp(
scaffoldMessengerKey: scaffoldMessengerKey,
home: Scaffold(
appBar: AppBar(title: const Text('Omniware Payment Playground')),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Running on: $_platformVersion • Plugin: ${FlutterPaymentPlugin.version}'),
pad,
_field('Base URL', _baseUrlCtl),
pad,
_field('API Key', _apiKeyCtl),
pad,
_field('SALT', _saltCtl),
pad,
Row(children: [
Expanded(child: _field('Order ID', _orderIdCtl)),
const SizedBox(width: 12),
ElevatedButton(onPressed: _regenOrderId, child: const Text('Generate Order ID')),
]),
pad,
Row(children: [
Expanded(
child: InputDecorator(
decoration: const InputDecoration(labelText: 'Mode', border: OutlineInputBorder()),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: _mode,
items: const [
DropdownMenuItem(value: 'LIVE', child: Text('LIVE')),
DropdownMenuItem(value: 'TEST', child: Text('TEST')),
],
onChanged: (v) => setState(() => _mode = v ?? 'LIVE'),
),
),
),
),
const SizedBox(width: 12),
Expanded(child: _field('Amount', _amountCtl, type: TextInputType.number)),
]),
pad,
_field('Name', _nameCtl),
pad,
Row(children: [
Expanded(child: _field('Phone', _phoneCtl, type: TextInputType.phone)),
const SizedBox(width: 12),
Expanded(child: _field('Email', _emailCtl, type: TextInputType.emailAddress)),
]),
pad,
_field('Return URL', _returnUrlCtl, type: TextInputType.url),
pad,
_field('Description', _descCtl),
pad,
Row(children: [
Expanded(child: _field('Currency', _currencyCtl)),
const SizedBox(width: 12),
Expanded(child: _field('Country', _countryCtl)),
]),
pad,
Row(children: [
Expanded(child: _field('City', _cityCtl)),
const SizedBox(width: 12),
Expanded(child: _field('State', _stateCtl)),
]),
pad,
Row(children: [
Expanded(child: _field('Address Line 1', _addr1Ctl)),
const SizedBox(width: 12),
Expanded(child: _field('Address Line 2', _addr2Ctl)),
]),
pad,
Row(children: [
Expanded(child: _field('ZIP Code', _zipCtl)),
const SizedBox(width: 12),
Expanded(child: _field('Enable Auto Refund (y/n)', _enableAutoRefundCtl)),
]),
pad,
Row(
children: [
Expanded(child: _field('Generated Hash (auto-upper)', TextEditingController(text: _hash), readOnly: true)),
const SizedBox(width: 12),
ElevatedButton(onPressed: _generateHash, child: const Text('Generate Hash')),
],
),
pad,
ElevatedButton(
onPressed: _startPayment,
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 14)),
child: const Text('Start Payment'),
),
const SizedBox(height: 24),
if (_lastStatusTitle.isNotEmpty || _lastResponseJson.isNotEmpty) ...[
Text(_lastStatusTitle, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(8),
),
child: SelectableText(
_lastResponseJson,
style: const TextStyle(fontFamily: 'monospace', color: Colors.white),
),
),
const SizedBox(height: 24),
],
],
),
),
),
),
);
}
}