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.
sure_pay_plugin #
A Flutter plugin for integrating SurePay POS payment terminals over TCP/IP.
Wraps the official sureposlibrary-release.aar Android SDK (TcpClient) with a clean, typed Dart API using a Kotlin MethodChannel bridge. Supports purchases, reversals, reconciliation, authorization pre-auth workflows, and refunds — all with strongly-typed responses and exceptions.
Table of contents #
- Features
- Installation
- Terminal setup
- Quick start
- API reference
- Response models
- Error handling
- Error code reference
- Wire protocol reference
- Integration with BLoC
- FAQ
Features #
| Operation | Method | FID |
|---|---|---|
| Get SDK version | getSdkVersion() |
— |
| Check terminal ready | checkTerminalReady() |
0001 |
| Get terminal info | getTerminalInfo() |
0010 |
| Purchase | purchase() |
0005 |
| Reversal | reverse() |
0003 |
| Get last transaction | getLastTransaction() |
0002 |
| Reconciliation | reconciliation() |
0008 |
| Authorization | authorization() |
0011 |
| Authorization void | authorizationVoid() |
0012 |
| Authorization extension | authorizationExtension() |
0013 |
| Purchase advice partial | purchaseAdvicePartial() |
0014 |
| Purchase advice full | purchaseAdviceFull() |
0015 |
| Refund with password | refundWithPassword() |
0017 |
| Refund with client ref + password | refundClientRefWithPassword() |
0018 |
| Cancel | cancel() |
— |
Platform support: Android only (requires SurePay AAR SDK).
Installation #
1 — Add the dependency #
dependencies:
sure_pay_plugin: ^0.0.1
Then run:
flutter pub get
2 — Place the AAR #
The sureposlibrary-release.aar must be present at:
<your_project>/android/libs/sureposlibrary-release.aar
The AAR is bundled inside the plugin and resolved automatically via the local Maven repository configured in
build.gradle. No extra steps needed.
3 — Internet permission #
Add to your app's android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4 — Minimum SDK #
Your app's android/app/build.gradle must set minSdkVersion 21 or higher.
Terminal setup #
Linux POS (port 7070) #
- Press Backspace or F2 → select Admin Menu
- Enter admin password:
145636987 - Scroll to INTEGRATION SETTINGS → select ENABLE → POS restarts
- Repeat steps 1–2, go to INTEGRATION SETTINGS → MEDIA → TCP/IP → POS restarts
Get the terminal IP:
- Press F1 → Internet → find the TIP (dynamic IP) field
Port: always 7070
Quick start #
import 'package:sure_pay_plugin/sure_pay_plugin.dart';
// 1. Create the repository once (e.g. in your service locator / DI)
final terminal = SurePayRepository(
ip: '192.168.1.100', // terminal IP from F1 → Internet → TIP
port: 7070,
clientKey: 'MY_APP_KEY',
);
// 2. Make a purchase
try {
final tx = await terminal.purchase(
amount: '10.25',
reference: 'ORDER-001',
);
if (tx.isApproved) {
print('Approved!');
print('Card : ${tx.cardNumber}'); // 506968******1986
print('Scheme : ${tx.schemaName}'); // mada
print('Auth : ${tx.authorizeCode}');
print('RRN : ${tx.rrn}');
} else {
print('Declined: resp=${tx.responseCode}');
}
} on SurePayTerminalException catch (e) {
print('Terminal error: ${e.code}');
// e.g. CANCEL_KRY_PRESSED, INSERT_TIMEOUT, BUSY_TERMINAL
} on SurePayConnectionException catch (e) {
print('Connection error: ${e.code}');
// FAILED_SEND or FAILED_RECEIVE
}
API reference #
All methods are on SurePayRepository. All return Future and throw typed exceptions.
getSdkVersion() #
final String version = await terminal.getSdkVersion();
// "3.0.7"
checkTerminalReady() #
final SurePayTerminalInfo info = await terminal.checkTerminalReady();
print(info.modelName); // "MP200"
getTerminalInfo() #
final SurePayTerminalInfo info = await terminal.getTerminalInfo();
print(info.terminalId); // "2304142404142404"
print(info.serialNumber); // "000315194814449"
print(info.simMnc); // "-1" (WiFi) or "01" (STC)
purchase() #
final SurePayTransaction tx = await terminal.purchase(
amount: '10.25', // required — decimal string in SAR
reference: 'INV-001', // optional — echoed back in tx.reference
);
reverse() #
final SurePayTransaction tx = await terminal.reverse();
// Reverses the last transaction (within terminal timer window)
getLastTransaction() #
final SurePayTransaction tx = await terminal.getLastTransaction();
reconciliation() #
final SurePayReconciliation r = await terminal.reconciliation();
print(r.isInBalance); // true / false
print(r.message); // "In Balance" / "OUT Of Balance"
authorization() #
final SurePayTransaction tx = await terminal.authorization(
amount: '100.00',
);
// Save tx.rrn, tx.authorizeCode, tx.date for later capture/void
authorizationVoid() #
final SurePayTransaction tx = await terminal.authorizationVoid(
amount: '100.00',
rrn: tx.rrn!,
dateTime: '12102022', // DDMMYYYY from original auth
authCode: tx.authorizeCode!,
);
authorizationExtension() #
final SurePayTransaction tx = await terminal.authorizationExtension(
rrn: '123456789123',
dateTime: '12102022', // DDMMYYYY
authCode: '097635',
);
purchaseAdvicePartial() #
final SurePayTransaction tx = await terminal.purchaseAdvicePartial(
amount: '75.00', // actual charged amount (less than auth)
rrn: '123456789123',
dateTime: '12102022',
authCode: '097635',
);
purchaseAdviceFull() #
final SurePayTransaction tx = await terminal.purchaseAdviceFull(
amount: '100.00', // full authorized amount
rrn: '123456789123',
dateTime: '12102022',
authCode: '097635',
);
refundWithPassword() #
final SurePayTransaction tx = await terminal.refundWithPassword(
amount: '5.00',
rrn: '162657000078',
date: '20221107', // YYYYMMDD
password: '14789', // 5-digit manager password
);
refundClientRefWithPassword() #
final SurePayTransaction tx = await terminal.refundClientRefWithPassword(
amount: '5.00',
clientRef: 'SUREPAY-000123456789', // max 20 chars
rrn: '162657000078',
date: '20221107',
password: '14789',
);
cancel() #
await terminal.cancel();
// Cancels any pending in-progress request
Response models #
SurePayTransaction #
| Field | Type | Description |
|---|---|---|
flag |
TransactionFlag |
approved / declined / voided |
isApproved |
bool |
true when flag=approved AND responseCode="000" |
cardNumber |
String? |
Masked PAN e.g. 506968******1986 |
schemaName |
String? |
Card scheme: mada, VISA, MASTER |
date |
String? |
YYMMDDHHmmss e.g. 220328162707 |
authorizeCode |
String? |
Auth code. Empty if declined. |
responseCode |
String? |
"000" = approved |
rrn |
String? |
Reference number e.g. 162657000078 |
merchantId |
String? |
Merchant ID |
amount |
String? |
Decimal string e.g. "0.01" |
terminalId |
String? |
Terminal ID |
reference |
String? |
Echo of your client reference |
SurePayReconciliation #
| Field | Type | Description |
|---|---|---|
isInBalance |
bool |
true when result = "003" |
result |
String? |
"003" = In Balance, "004" = Out Of Balance |
message |
String? |
"In Balance" / "OUT Of Balance" |
SurePayTerminalInfo #
| Field | Type | Description |
|---|---|---|
modelName |
String? |
MP200, VEGA3000, UPT1000 |
terminalId |
String? |
Terminal ID |
serialNumber |
String? |
Serial number |
simMnc |
String? |
-1=WiFi/Eth, 01=STC, 03=Mobily, 04=Zain, 06=Lebara |
raw |
String? |
Original pipe-delimited string |
Error handling #
try {
final tx = await terminal.purchase(amount: '10.25');
} on SurePayTerminalException catch (e) {
// Terminal returned an error — see TerminalErrorCode constants
switch (e.code) {
case TerminalErrorCode.cancelKeyPressed:
showMessage('Customer cancelled');
break;
case TerminalErrorCode.insertTimeout:
showMessage('Card not presented in time');
break;
case TerminalErrorCode.busyTerminal:
showMessage('Terminal is busy, please wait');
break;
default:
showMessage('Terminal error: ${e.code}');
}
} on SurePayConnectionException catch (e) {
// TCP/IP communication failure
showMessage('Cannot reach terminal: ${e.code}');
} on SurePayArgumentException catch (e) {
// Programming error — a required argument was missing
showMessage('Invalid request: ${e.message}');
}
Error code reference #
Terminal errors (SurePayTerminalException.code) #
Use the TerminalErrorCode constants:
| Constant | Value | Description |
|---|---|---|
TerminalErrorCode.pinCanceled |
PIN_CANCELED |
Customer cancelled PIN entry |
TerminalErrorCode.amountNotFound |
AMOUNT_NOT_FOUND |
Amount field missing |
TerminalErrorCode.insertTimeout |
INSERT_TIMEOUT |
Card not inserted in time |
TerminalErrorCode.insertCanceled |
INSERT_CANCELED |
Card insertion cancelled |
TerminalErrorCode.cancelKeyPressed |
CANCEL_KRY_PRESSED |
Cancel key pressed on terminal |
TerminalErrorCode.cancelConfirmAmount |
CANCEL_CONFIRM_AMOUNT |
Amount confirmation cancelled |
TerminalErrorCode.iccTransactionFailed |
ICC_TRANSACTION_FAILED |
Chip card transaction failed |
TerminalErrorCode.transactionNotAllowed |
TRANSACTION_NOT_ALLOWED |
Transaction type not allowed |
TerminalErrorCode.manualEntryNotAllowed |
MANUAL_INTER_NOT_ALLOWED |
Manual card entry disabled |
TerminalErrorCode.functionNotSupported |
FUNCTION_NOT_SUPPORTED |
Function not supported by terminal |
TerminalErrorCode.noIncomeRequest |
NO_INCOME_REQUEST |
No incoming request available |
TerminalErrorCode.noRecords |
NO_RECORDS |
No transaction records found |
TerminalErrorCode.notSupported |
NOT_SUPPORTED |
Not supported |
TerminalErrorCode.checkConnection |
CHECK_CONNECTION |
Terminal not reachable |
TerminalErrorCode.noReverse |
NO_REVERSE |
No reversal available |
TerminalErrorCode.reversalTimerExpired |
REVERSAL_TIMER_EXPIRED |
Reversal window expired |
TerminalErrorCode.busyTerminal |
BUSY_TERMINAL |
Terminal is processing another request |
TerminalErrorCode.unknownError |
UNKNOWN_ERROR |
Unclassified error |
TerminalErrorCode.msgFormatError |
MSG_FORMAT_ERROR |
Malformed message |
TerminalErrorCode.invalidIp |
INTEGRATION_ERR_INVALID_IP |
Invalid IP address |
TerminalErrorCode.invalidClientKey |
INTEGRATION_ERR_INVALID_CLIENT_KEY |
Invalid client key |
Connection errors (SurePayConnectionException.code) #
| Value | Description |
|---|---|
FAILED_SEND |
Could not send data to terminal |
FAILED_RECEIVE |
Could not receive response from terminal |
Wire protocol reference #
The SDK handles the TCP/IP framing internally. For reference the raw protocol is:
Request : 0x02 + ASCII data bytes + 0x03
Response : 0x02 + ASCII data bytes + 0x03
Purchase request (SAR 10.25):
02 30 30 30 35 30 30 30 30 30 30 30 30 30 31 30 32 35 03
STX "0005" "000000010250" (in smallest unit) ETX
Successful response (pipe-delimited):
"0|506968******1986|mada|220328162707|097635|000|162657000078|201075999568|0.01|2386300060647280"
^ flag=0 (approved)
^^^responseCode=000
Error response:
"-2" → ERR_CANCEL_KEY_PRESSED
"-59" → ERR_NO_CONNECTION
Integration with BLoC #
Example Cubit using this plugin:
// payment_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sure_pay_plugin/sure_pay_plugin.dart';
part 'payment_state.dart';
class PaymentCubit extends Cubit<PaymentState> {
final SurePayRepository _terminal;
PaymentCubit(this._terminal) : super(PaymentIdle());
Future<void> purchase({required String amount, required String reference}) async {
emit(PaymentLoading());
try {
final tx = await _terminal.purchase(amount: amount, reference: reference);
if (tx.isApproved) {
emit(PaymentApproved(tx));
} else {
emit(PaymentDeclined(tx));
}
} on SurePayTerminalException catch (e) {
emit(PaymentError(e.code));
} on SurePayConnectionException catch (e) {
emit(PaymentError(e.code));
}
}
}
// payment_state.dart
abstract class PaymentState {}
class PaymentIdle extends PaymentState {}
class PaymentLoading extends PaymentState {}
class PaymentApproved extends PaymentState { final SurePayTransaction tx; PaymentApproved(this.tx); }
class PaymentDeclined extends PaymentState { final SurePayTransaction tx; PaymentDeclined(this.tx); }
class PaymentError extends PaymentState { final String code; PaymentError(this.code); }
Provide it in your widget tree:
BlocProvider(
create: (_) => PaymentCubit(
SurePayRepository(ip: '192.168.1.100', port: 7070, clientKey: 'MY_KEY'),
),
child: CheckoutPage(),
)
FAQ #
Q: The terminal IP changes on every restart — how do I handle that?
A: On Linux POS, the IP is dynamic. Store it in your app's settings and let the cashier update it. You can also configure a static IP on the terminal's network settings or assign a DHCP reservation by MAC address on your router.
Q: I get FAILED_RECEIVE on every call.
A: Check that:
- The terminal and Android POS device are on the same network/VLAN.
- TCP/IP integration is enabled on the terminal (see Terminal Setup above).
- Port
7070is not blocked by a firewall. - No other app on the network is already connected to the terminal.
Q: Can I run two concurrent purchases?
A: No. The terminal processes one request at a time. The cancel() method can abort a stuck in-progress request.
Q: Does this work on iOS?
A: No. The plugin uses the SurePay Android AAR which has no iOS equivalent. iOS support would require a separate integration.
Q: What's the clientKey parameter?
A: It is your app's unique identifier sent with every request so the terminal can associate responses with the correct caller. Any non-empty string works — the sample app uses "test_900".
Q: How do I run the example app?
A:
cd example
flutter run
Enter your terminal's IP and port in the connection bar, then tap any operation button.