Flutter plugin for GP tom payments (Android App2App + iOS Deeplinks).
Important: You must call
GpTomManager.init()as the first method before using anything else.
isInstalled()register()transaction()(sale / storno / refund / closeBatch)closeBatch()/closeBatchLegacy()getState()getDetail()cancelPolling()(Android only — stops waiting on a state poll)- Event streams for results (sale / refund / cancel / closeBatch / state / detail)
See CHANGELOG.md for version history.
Add dependency to your pubspec.yaml:
dependencies:
gptom:
git:
url: https://github.com/AppsDevTeam/flutter-gptom.git
ref: v1.3.0Then run:
flutter pub getPin to a specific tag (
ref: v1.3.0) to avoid breakage when new versions land onmain.
After cloning this repo, enable git hooks:
git config core.hooksPath scripts/hooks✅ Always call init first, otherwise you will get notInitialized.
await GpTomManager.init(
GpTomInitOptions(
isDevelopment: true, // DEV / PROD
debugLogs: true,
iosRedirectScheme: "myapp://gptom", // iOS only (required on iOS)
),
);GpTomManager.saleResults.listen((r) {
print("EVENT SALE: $r");
});
GpTomManager.refundResults.listen((r) {
print("EVENT REFUND: $r");
});
GpTomManager.cancelResults.listen((r) {
print("EVENT CANCEL: $r");
});
GpTomManager.closeBatchResults.listen((r) {
print("EVENT CLOSE_BATCH: $r");
});
GpTomManager.stateResults.listen((r) {
print("EVENT STATE: $r");
});
GpTomManager.detailResults.listen((r) {
print("EVENT DETAIL: $r");
});final res = await GpTomManager.isInstalled();
print(res);GP tom requires a transactionId for each transaction, so you typically call register first:
final reg = await GpTomManager.register(
GpTomRegisterRequest(
originReferenceNum: "flutter_ref_1",
clientId: "optional-client-id",
),
);
final txId = reg.data?.transactionId;
print("transactionId=$txId");final res = await GpTomManager.transaction(
GpTomTransactionRequest.sale(
transactionId: txId!,
amount: 100, // minor units
originReferenceNum: "flutter_ref_1",
),
);
print(res);final res = await GpTomManager.storno(
GpTomTransactionRequest.storno(
transactionId: txId!,
cancelMode: GpTomCancelMode.olderTransaction,
originTransactionId: "transactionIdToCancel",
),
);
print(res);Refund is a standalone return of funds (not linked to an original transaction).
final res = await GpTomManager.refund(
GpTomTransactionRequest.refund(
transactionId: txId!,
amount: 100, // minor units
originReferenceNum: "flutter_ref_1",
),
);
print(res);final res = await GpTomManager.closeBatch();
print(res);On Android closeBatch() resolves the result via state polling + transactionInquire,
which returns minimal data (no batch totals like saleAmount, voidAmount, ...).
On iOS it opens the batch/close deeplink and reads the full Batch from the response.
If you need full batch totals on Android (saleAmount, totalAmount, voidAmount, ...), use the legacy V2 callback flow:
final res = await GpTomManager.closeBatchLegacy();
print(res);- Android: bypasses state polling and reads
TransactionResultV2Entitydirectly viaBatchMapper. - iOS: identical to
closeBatch()(deeplink flow).
Both methods deliver the result via the closeBatchResults stream.
To allow the plugin to detect if GP tom app is installed using canOpenURL, add gptom into LSApplicationQueriesSchemes in your app’s ios/Runner/Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>gptom</string>
</array>Without this,
canOpenURL("gptom://")will always returnfalse.
GP tom returns back to your app using deeplinks, so you must register your app scheme in Info.plist.
Example for scheme myapp:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>Then provide it to init:
await GpTomManager.init(
GpTomInitOptions(
iosRedirectScheme: "myapp://gptom",
),
);Android uses App2App (AIDL) integration.
No special manifest setup is required besides having GP tom installed.
Current bundled AAR version: 1.28.0
Android resolves transaction results via periodic state polling (every 500 ms, 5 min timeout). When state == COMPLETED, the plugin calls transactionInquire to fetch the full result. You can stop the wait early:
await GpTomManager.cancelPolling(transactionId);The matching result stream receives a cancelled event. iOS returns unsupportedOnPlatform (no polling there).
Each GpTomResult returns two kinds of codes:
Tells you whether the plugin call itself succeeded or how it failed.
| Code | Meaning |
|---|---|
ok |
Call succeeded; result.data is populated |
cancelled |
User cancelled the operation (PIN cancel, polling cancel, deeplink cancel) |
timeout |
Operation timed out (Android polling > 5 min) |
failed |
Generic failure (declined, refused, ...) |
internalError |
Unexpected error (parse error, native crash, ...) |
notInitialized |
GpTomManager.init() wasn't called |
notInstalled |
GP tom app is not installed |
unsupportedOnPlatform |
Feature not available on this OS (e.g. cancelPolling on iOS) |
invalidArgument |
Missing or malformed request parameter |
invalidDeeplink |
iOS deeplink could not be parsed |
invalidClientId |
clientId rejected by GP tom |
invalidCredentials |
User logged out, wrong password, expired session |
invalidCode |
Auth code invalid or expired |
invalidUserName |
Wrong username |
invalidAmount |
Amount out of range |
passwordChangeRequired |
Password change required before continuing |
passwordPendingConfirmation |
Password change is pending confirmation |
merchantInfoMissing |
Merchant configuration missing |
tidNotAssignedToThisUser |
TID not assigned / not active / not selected |
tidAlreadyOccupied |
TID already reserved by another user/device |
anotherTidUsedOnThisDevice |
Different TID is already in use on this device |
terminalSetupFailed |
Terminal setup failed |
cannotBeVoided |
Transaction already cancelled / completed and not voidable |
unsupportedTransactionOperationOrType |
Operation/type not allowed for this merchant |
networkError |
Network unavailable / HTTP error |
failedTapToPay |
SoftPOS / tap-to-pay error |
failedToCloseBatch |
Batch close was declined |
serviceBindFailed |
Could not bind to GP tom AIDL service (Android) |
For transactions (saleResults, refundResults, cancelResults), the ECR code carries the transaction outcome:
| Code | Name | Meaning |
|---|---|---|
0 |
ecrSuccess |
Transaction was successful |
-1 |
ecrFailed |
Transaction failed |
-2 |
ecrTransactionIdInvalid |
Invalid transaction ID |
-3 |
ecrTransactionNotFound |
Transaction not found (you can retry) |
-4 |
ecrTransactionDecline |
Transaction declined |
-5 |
ecrTransactionAlreadyVoided |
Transaction was already cancelled |
-6 |
ecrParameterInvalid |
Invalid parameters |
-7 |
ecrUnauthorized |
User not authorized |
-8 |
ecrNotAllowed |
Operation not allowed |
-9 |
ecrWrongStatus |
Input from user needed in GP tom app |
See GP tom result codes for full reference.
if (result.code != GpTomResultCode.ok) {
// plugin / communication error
showError(result.message);
} else if (result.data?.ecrResultCode != GpTomEcrResultCode.ecrSuccess) {
// transaction was processed but not approved
showDeclined(result.data?.responseMessage);
} else {
// success
showSuccess(result.data!);
}transactionIdis required by GP tom for each transaction.- For
stornoyou typically needoriginTransactionId(the original transaction to cancel). - Results are delivered through event streams.
This plugin bundles GP tom iOS SDK (MIT License).
Source: https://github.com/GP-tom/tom-ios-sdk
Bundled commit: 7f6e963 (2026-04-28)
See THIRD_PARTY_NOTICES.md for details.