sure_pay_plugin 0.0.1
sure_pay_plugin: ^0.0.1 copied to clipboard
A Flutter plugin for integrating SurePay POS payment terminals over TCP/IP. Supports purchases, reversals, reconciliation, authorization, and refunds via a clean typed Dart API.
// example/lib/main.dart
//
// Full demo application for the sure_pay_plugin.
// Shows every supported terminal operation with a live log output.
import 'package:flutter/material.dart';
import 'package:sure_pay_plugin/sure_pay_plugin.dart';
void main() => runApp(const SurePayDemoApp());
// ─── App ──────────────────────────────────────────────────────────────────────
class SurePayDemoApp extends StatelessWidget {
const SurePayDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SurePay POS Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
home: const TerminalDemoPage(),
);
}
}
// ─── Page ─────────────────────────────────────────────────────────────────────
class TerminalDemoPage extends StatefulWidget {
const TerminalDemoPage({super.key});
@override
State<TerminalDemoPage> createState() => _TerminalDemoPageState();
}
class _TerminalDemoPageState extends State<TerminalDemoPage> {
// Connection fields
final _ipCtrl = TextEditingController(text: '192.168.1.100');
final _portCtrl = TextEditingController(text: '7070');
// Log entries (newest first)
final List<_LogEntry> _log = [];
bool _busy = false;
// ─── Repository factory ────────────────────────────────────────────────────
SurePayRepository get _repo => SurePayRepository(
ip: _ipCtrl.text.trim(),
port: int.tryParse(_portCtrl.text.trim()) ?? 7070,
);
// ─── Logging ───────────────────────────────────────────────────────────────
void _log$(String msg, {bool error = false}) {
setState(() => _log.insert(0, _LogEntry(msg, error: error)));
}
// ─── Runner ────────────────────────────────────────────────────────────────
Future<void> _run(String label, Future<void> Function() action) async {
if (_busy) return;
setState(() => _busy = true);
_log$('▶ $label');
try {
await action();
} on SurePayTerminalException catch (e) {
_log$('❌ Terminal: ${e.code}', error: true);
} on SurePayConnectionException catch (e) {
_log$('🔌 Connection: ${e.code}', error: true);
} on SurePayArgumentException catch (e) {
_log$('⚠️ Args: ${e.message}', error: true);
} catch (e) {
_log$('💥 $e', error: true);
} finally {
setState(() => _busy = false);
}
}
// ─── Formatters ────────────────────────────────────────────────────────────
void _printTx(SurePayTransaction tx) {
if (tx.isApproved) {
_log$(
'✅ APPROVED | ${tx.schemaName} ${tx.cardNumber} '
'| auth=${tx.authorizeCode} | rrn=${tx.rrn} | ₺${tx.amount}',
);
} else {
_log$('❌ ${tx.flag.name.toUpperCase()} | resp=${tx.responseCode}', error: true);
}
}
// ─── Terminal operations ───────────────────────────────────────────────────
void _getSdkVersion() => _run('getSdkVersion', () async {
final v = await _repo.getSdkVersion();
_log$('✅ SDK version: $v');
});
void _checkReady() => _run('checkTerminalReady', () async {
final info = await _repo.checkTerminalReady();
_log$('✅ Ready | model=${info.modelName} | id=${info.terminalId}');
});
void _getTerminalInfo() => _run('getTerminalInfo', () async {
final info = await _repo.getTerminalInfo();
_log$('✅ $info');
});
void _purchase() => _run('purchase SAR 10.25', () async {
final tx = await _repo.purchase(amount: '10.25', reference: 'ORDER-001');
_printTx(tx);
});
void _reverse() => _run('reverse', () async {
final tx = await _repo.reverse();
_printTx(tx);
});
void _getLastTrx() => _run('getLastTransaction', () async {
final tx = await _repo.getLastTransaction();
_printTx(tx);
});
void _reconciliation() => _run('reconciliation', () async {
final r = await _repo.reconciliation();
_log$('✅ Reconciliation: ${r.message ?? (r.isInBalance ? "In Balance" : "Out Of Balance")}');
});
void _authorization() => _run('authorization SAR 50.00', () async {
final tx = await _repo.authorization(amount: '50.00');
_printTx(tx);
});
void _authVoid() => _run('authorizationVoid', () async {
final tx = await _repo.authorizationVoid(
amount: '50.00',
rrn: '123456789123',
dateTime: '12102022',
authCode: '123456',
);
_printTx(tx);
});
void _authExtension() => _run('authorizationExtension', () async {
final tx = await _repo.authorizationExtension(rrn: '123456789123', dateTime: '12102022', authCode: '123456');
_printTx(tx);
});
void _advicePartial() => _run('purchaseAdvicePartial', () async {
final tx = await _repo.purchaseAdvicePartial(
amount: '30.00',
rrn: '123456789123',
dateTime: '12102022',
authCode: '123456',
);
_printTx(tx);
});
void _adviceFull() => _run('purchaseAdviceFull', () async {
final tx = await _repo.purchaseAdviceFull(
amount: '50.00',
rrn: '123456789123',
dateTime: '12102022',
authCode: '123456',
);
_printTx(tx);
});
void _refundWithPwd() => _run('refundWithPassword SAR 5.00', () async {
final tx = await _repo.refundWithPassword(amount: '5.00', rrn: '123456789123', date: '20221107', password: '14789');
_printTx(tx);
});
void _refundClientRef() => _run('refundClientRefWithPassword', () async {
final tx = await _repo.refundClientRefWithPassword(
amount: '5.00',
clientRef: 'SUREPAY-000123456789',
rrn: '123456789123',
date: '20221107',
password: '14789',
);
_printTx(tx);
});
void _cancel() => _run('cancel', () async {
await _repo.cancel();
_log$('✅ Cancel request sent');
});
// ─── Build ─────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SurePay POS Plugin Demo'), centerTitle: true),
body: Column(
children: [
// ── Connection inputs ──────────────────────────────────────────────
_ConnectionBar(ipCtrl: _ipCtrl, portCtrl: _portCtrl, busy: _busy),
const Divider(height: 1),
// ── Operation buttons ──────────────────────────────────────────────
Expanded(
flex: 3,
child: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_section('Terminal'),
_buttonRow([
_btn('SDK Version', _getSdkVersion),
_btn('Check Ready', _checkReady),
_btn('Terminal Info', _getTerminalInfo),
]),
_section('Purchase & History'),
_buttonRow([
_btn('Purchase', _purchase, color: Colors.teal),
_btn('Reverse', _reverse, color: Colors.orange),
_btn('Last Trx', _getLastTrx),
_btn('Reconcile', _reconciliation),
]),
_section('Authorization'),
_buttonRow([
_btn('Auth', _authorization),
_btn('Auth Void', _authVoid),
_btn('Auth Extend', _authExtension),
_btn('Advice Part.', _advicePartial),
_btn('Advice Full', _adviceFull),
]),
_section('Refund'),
_buttonRow([_btn('Refund + Pwd', _refundWithPwd), _btn('Refund ClientRef', _refundClientRef)]),
_section('Control'),
_buttonRow([_btn('Cancel', _cancel, color: Colors.red)]),
],
),
),
),
const Divider(height: 1),
// ── Log output ─────────────────────────────────────────────────────
Expanded(
flex: 2,
child: _LogView(entries: _log, busy: _busy),
),
],
),
);
}
// ─── UI helpers ────────────────────────────────────────────────────────────
Widget _section(String title) => Padding(
padding: const EdgeInsets.only(top: 8, bottom: 4),
child: Text(
title,
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Colors.grey),
),
);
Widget _buttonRow(List<Widget> children) => Wrap(spacing: 6, runSpacing: 6, children: children);
Widget _btn(String label, VoidCallback onTap, {Color? color}) => ElevatedButton(
onPressed: _busy ? null : onTap,
style: ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: color != null ? Colors.white : null,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
textStyle: const TextStyle(fontSize: 12),
),
child: Text(label),
);
}
// ─── Connection bar ───────────────────────────────────────────────────────────
class _ConnectionBar extends StatelessWidget {
final TextEditingController ipCtrl;
final TextEditingController portCtrl;
final bool busy;
const _ConnectionBar({required this.ipCtrl, required this.portCtrl, required this.busy});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: Row(
children: [
Expanded(
child: TextField(
controller: ipCtrl,
enabled: !busy,
decoration: const InputDecoration(
labelText: 'Terminal IP',
hintText: '192.168.1.100',
border: OutlineInputBorder(),
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
),
),
),
const SizedBox(width: 8),
SizedBox(
width: 80,
child: TextField(
controller: portCtrl,
enabled: !busy,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Port',
hintText: '7070',
border: OutlineInputBorder(),
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
),
),
),
],
),
);
}
}
// ─── Log view ─────────────────────────────────────────────────────────────────
class _LogEntry {
final String text;
final bool error;
final DateTime time;
_LogEntry(this.text, {this.error = false}) : time = DateTime.now();
}
class _LogView extends StatelessWidget {
final List<_LogEntry> entries;
final bool busy;
const _LogView({required this.entries, required this.busy});
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xFF1A1A2E),
child: Column(
children: [
if (busy) const LinearProgressIndicator(minHeight: 2) else const SizedBox(height: 2),
Expanded(
child: entries.isEmpty
? const Center(
child: Text('Tap a button to start', style: TextStyle(color: Colors.grey, fontSize: 13)),
)
: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (_, i) {
final e = entries[i];
final hh = e.time.hour.toString().padLeft(2, '0');
final mm = e.time.minute.toString().padLeft(2, '0');
final ss = e.time.second.toString().padLeft(2, '0');
return Padding(
padding: const EdgeInsets.symmetric(vertical: 1),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$hh:$mm:$ss ',
style: const TextStyle(color: Colors.grey, fontSize: 10, fontFamily: 'monospace'),
),
Expanded(
child: Text(
e.text,
style: TextStyle(
color: e.error ? Colors.redAccent : Colors.greenAccent,
fontSize: 11,
fontFamily: 'monospace',
),
),
),
],
),
);
},
),
),
],
),
);
}
}