amwal_ecr 0.4.0
amwal_ecr: ^0.4.0 copied to clipboard
Drive an Amwal POS terminal from Flutter. One Dart API over native Android and iOS ECR SDKs (Wi‑Fi, USB, Web Service, and Android app-to-app), plus a pure-Dart Windows host (LAN TCP + Web Service): sa [...]
Changelog #
All notable changes to amwal_ecr. This project follows semantic versioning,
with the addition described in the release policy: any
change to the platform-channel contract is breaking, and any change to what an
outcome means is breaking, however small the diff.
0.4.0 #
Port of iOS AmwalECR 0.3.0 and Android ecr-sdk 1.0.8 Web Service
contract changes (CocoaPods / SPM working tree + Kotlin package).
Changed (breaking) #
- Web Service inquiry transport failure — when an inquiry (
inquire/inquireByReference) gets no usable answer (timeout, socket error, empty or invalid body, HTTP 5xx), the result is nowEcrInquiryFailedwithEcrConnectionLostinstead ofEcrInquiryNotFound. "Not found" now means the terminal answered and has no such transaction; a transport failure means the outcome is still unknown. Reconcile, do not retry. - Sale / void / refund unknown outcomes are
EcrFailed, notEcrDeclined. Asuccess: falseenvelope withnextStep: INQUIRE_BY_MERCHANT_REFERENCE(typicallyNO_RESPONSE/ timeout wording) isEcrFailedwithEcrTimeoutorEcrConnectionLost. An ordinary decline without that next step is unchanged. Money may have moved — inquire by reference; do not treat it as "no money taken" and retry the sale. Applies on the Dart Windows host and matches native AmwalECR / ecr-sdk readers.
Added #
- iOS
AmwalECR0.3.0 — plugin pins (ecrSdkVersion=0.3.0; podspec~> 0.3.0, SwiftPM.upToNextMinor(from: "0.3.0")). Androidecr-sdkis pinned to 1.0.8; the Gradle fallback version is raised from 1.0.5 to 1.0.8. - Web Service default read timeout is 130s
(
EcrConfig.webServiceResponseTimeout).EcrTerminal/EcrSessions.openapply it when the config still has the LAN default of 120s; an explicit override is kept. Passed to native hosts viaresponseTimeoutMs. - Dart Web Service parity (Windows host) with iOS 0.3.0 / Android 1.0.8:
requestDateTimealways equalstransactionDateTimein request bodies.- Void / refund send
originalMerchantReference+originalTerminalId; inquiry-by-reference sendsoriginalMerchantReferencewith emptystan. - Inquiry path is
/Ecr/Inquiry. - Synthesized failures (timeout, socket error, empty / invalid body, HTTP
5xx) carry
nextStep: INQUIRE_BY_MERCHANT_REFERENCE; 4xx synthesized errors do not. - Inquiry answers may be flat or nested: the transaction is read from
dataonly when it carries transaction fields, otherwise from the response root; a bare correlation echo isEcrInquiryNotFound. The reference resolves from the transaction payload, then the envelope, then the request. - Environment endpoints updated for the UAT environment (
:25453).
0.3.4 #
Fixed #
- Web Service
inquireByReference— no longer refused at the Dart API because Web Service has no reachability probe. Matches KotlinEcrWebServiceTerminal.inquireByReference. - Web Service wire bodies — inquiry / void / refund now carry
originalMerchantReference,originalTerminalId, andrequestDateTimelikeecr-sdkEcrWebServiceMessage(inquiry by reference setsoriginalMerchantReference; STAN inquiry leaves it empty).
0.3.3 #
Added #
- App-to-app
printReceipt— Android only. Sendscom.amwalpay.pos.ecr.PRINTwith a required receipt image (width 384 px, size ≤ 15 MB). ReturnsEcrPrintOutcomewith status and message as separate fields. Wi‑Fi, USB cable and Web Service refuse before the host is called; iOS / Windows answer the same way. Channel methodprintReceipt; plugin<queries>includes PRINT. - Example till shows Print receipt on approved app-to-app results only
(
example/lib/print/builds the bilingual 384 px PNG). - Pins Android host to
com.amwal-pay:ecr-sdk1.0.7 (app-to-app PRINT contract documented with the SDK).
Still platform-limited #
- USB cable and app to app remain Android-only. Print is app to app only.
0.3.2 #
Added / fixed #
- iOS
signOn— the bridge calls nativeAmwalECRsign-on over Wi‑Fi and USB cable (same transports as Android). Published0.3.1on pub.dev still answers every iOSsignOnwith a typed unsupported stub; point a path dependency at this checkout (asexample/andAmwalECR-flutter-exampledo) to get the real answer. - iOS
closeReceipt— same as Android: Wi‑Fi / USB cable ask the terminal to dismiss its on-screen receipt (idle/ declined / failed). Web Service and app to app still answer typed unsupported before anything is sent. - Example apps show terminal readiness from sign-on (permitted operations, reported transport / terminal name).
Still platform-limited #
- USB cable and app to app remain Android-only.
0.3.1 #
CI only: the publish check no longer fails on the tracked example lockfile. No code or API changes since 0.3.0 (which was never published to pub.dev).
0.3.0 #
Adds a fourth transport and a Windows host in the same release.
Breaking, in the way an enum is breaking. EcrTransport gains
appToApp, so an exhaustive switch over it in your code stops compiling
until it has a branch. The Windows host does not change the Dart API for
Wi‑Fi or Web Service: the same EcrSessions.open / EcrTerminal path works
on Android, iOS, and Windows. USB cable and app to app remain Android-only.
Added #
EcrTransport.appToApp(ecrMode5, channel nameapp_to_app) andEcrTerminal.appToApp, which takes the payment app's application id instead of an address. An address passed there is refused at construction.EcrPaymentApp.defaultPackage— the application id of the terminal build, overridable for a test build.EcrOpenedSession.usesPaymentApp, andEcrTransport.hasReachabilityProbe/.supportsReceipt, which replace two open-coded predicate pairs that were one answer for three transports and are not for the fourth.- The Android host is now
ActivityAware, and the plugin's manifest declares the payment app in<queries>. - Windows support via a pure-Dart
AmwalEcrPlatform(DartIoAmwalEcrPlatform): LAN TCP (Wi‑Fi) and Web Service Hub REST, registered withdartPluginClass: AmwalEcrWindows. USB cable and app to app return a typed unsupported failure on Windows. - Protocol engine under
lib/src/dart_io/— framing, LAN / Web Service HMAC signing, message build, response parse, auto-inquire-on-failure, and cancel by closing the socket / aborting HTTP. - Example app Windows runner + Codemagic
example-windowsworkflow; optional--dart-definelive config seeds (ECR_*) documented inexample/README.md. - Windows docs: Install / troubleshooting in the package README, full platform
matrix in
doc/compatibility-matrix.md, and Visual Studio ATL notes in both example READMEs (forflutter_secure_storage).
Worth knowing before you ship it #
- App to app is Android only. On iOS, Windows, and the web an operation is refused before anything is sent, with a message about the platform rather than about a port.
- An e-receipt can be fetched over app to app, as on the other local transports, at the cost of another handover the operator watches.
- An interrupted app-to-app round trip — the payment app destroyed before it
answered — is an unknown outcome pointing at
inquireByMerchantReference, never a decline. Send amerchantReferenceon every request: it is the only handle that survives. autoInquireOnFailureis forced off for app to app, because the inquiry is itself another trip through the payment app.
0.2.1 #
Brings the package level with com.amwal-pay:ecr-sdk 1.0.5 and AmwalECR
0.2.1 — the ECR modes release: Wi‑Fi as before, USB cable on Android, and the
Web Service (REST) transport on both platforms, all opened through one native
session path.
Added #
EcrOpenedSession/EcrSessions.open(Dart) — names the same session-open intent as nativeecr_sdk/ AmwalECR; wraps a transport-awareEcrTerminal.- Android / iOS hosts open sessions via native
EcrSessions.openso LAN, USB cable, and Web Service share one dispatch path. - Example apps store signing secrets with
flutter_secure_storage(Keychain / Keystore), with one-shot migration from plaintext SharedPreferences.
Changed #
EcrSecureHashKeysremoved. The plugin/SDK only acceptsEcrConfig.secureHashKey. Apps persist secrets and assign the value for the selected terminal mode.- Unit-test placeholders renamed to match
ecr_sdk:SECURE_HASH_KEY_ECR_WIFI,SECURE_HASH_KEY_ECR_WIFI_OTHER,SECURE_HASH_KEY_WEBSERVICEonEcrTestConfigs(lan/lanOther/webService). - Example iOS local SDK wiring matches Android:
ios/ecr_sdk.properties+example/ios/ecr_sdk.properties(project|cocoapods|spm), applied by./tool/sync_ios_ecr_sdk.sh/./tool/prepare_ios_example.shto both the podspec andPackage.swiftfaces. EcrTerminal.probeReachability()/EcrReachability. Matchesecr_sdkand AmwalECR: the same probe asisReachable(), plus host, port, endpoint and the underlying error when the link fails. Channel methodprobeReachability;isReachableremains as a convenience over it.- Android USB accessory channel discards stale framed answers whose nonce does not match the request just sent.
Changed #
- Channel transport still uses primary name
webService; acceptsweb_serviceas an alias without breaking existing callers.
Breaking #
ecrMode1 is USB cable. Matchesecr_sdk: DartEcrTransport.usbCable, channel name"usb_cable"(wire value1).isIpTransportis true only for Wi‑Fi. USB cable is driven on Android via AOA; iOS returns typed unsupported. There is no Ethernet transport or channel name.
0.2.0 #
Brings the package level with com.amwal-pay:ecr-sdk 1.0.4 and AmwalECR 0.2.0
— the same three features on both platforms, over the same channel contract.
Breaking. The protocol renamed the field that names a transaction, so
requestId is merchantReference everywhere: on EcrResult, EcrInquiry,
EcrReceipt and on the platform channel. Rename it at the call sites; the type
is unchanged and it still arrives filled in whether or not you supply one. The
channel contract changed too, so the Dart side and the native hosts must be
upgraded together — an old host does not lose a field, it stops being understood.
Added #
- A reference the till chooses.
merchantReferenceonsale,voidTransaction,refund,run,inquireandreceipt— an order number, a basket id, whatever already names the sale in your system. Pass it and the same string identifies the transaction in your books, in the terminal's records and in any later lookup; leave it out and the host generates one. Either way it comes back onEcrResult.merchantReference, and it is the only handle you hold if the answer never arrives. EcrTerminal.inquireByReference. The answer to an outcome you never received: looks a transaction up by the reference it was sent with, rather than by a receipt number that only arrives in the answer.EcrFailed.recovered,.recoveredTransactionand.settled. The host now follows a lost money-moving request with one inquiry and attaches what it found. Asettledfailure is a delivery that failed and an outcome that is known, sooutcomeIsUnknownis false and the till books the transaction instead of reconciling it. Turn the follow-up off withEcrConfig.autoInquireOnFailure. Nothing is ever re-sent.EcrConfig.secureHashKey. The secret this till shares with the terminal. Set it and the native SDK signs every request and checks every answer. Refused at construction if it is not an even-length hex string of at least 16 characters — on the shop floor a bad key is a decline the cashier cannot explain. Required in practice: a terminal refuses what it cannot verify. The key is never printed bytoString.EcrUnauthenticated, for an answer that cannot be shown to have come from the terminal. Leaves the outcome unknown, like a timeout — never a decline, and never to be retried.EcrNextStep, on everyEcrResult. What the terminal says to do about an outcome, rather than something a till has to infer from a response code. A decline that asks for an inquiry now reportsoutcomeIsUnknown.
Changed #
- Config, arguments and results carry the new fields across the channel:
secureHashKey,autoInquireOnFailure,merchantReference,originalMerchantReference,nextStep,recovered, and theunauthenticatedfailure kind. The frozen contract test lists them all. - A reference the wire format cannot carry — over 32 characters, or containing a
space,
&or=— throwsEcrArgumentErrorbefore anything is sent, on both platforms and in Dart, with the same message. - The Android host now requires
com.amwal-pay:ecr-sdk:1.0.4, and the iOS hostAmwalECR ~> 0.2.0.
0.1.0 #
First release.
Added #
- One Dart API over both platforms.
EcrTerminalwithisReachable,sale,voidTransaction,refund,inquireandreceipt, plus arunform for a till driving the operation from its own menu. - Typed outcomes. Sealed
EcrResult(EcrApproved/EcrDeclined/EcrFailed),EcrInquiryandEcrReceipt, each with its own branches, so an inquiry that failed cannot be mistaken for a sale that was refused. outcomeIsUnknownon every result. Set for timeouts, lost connections, unreadable answers, cancellations, and a decline carrying response code91. Nothing in the package retries a money-moving request, and this is the flag that tells a caller not to either.- Exact amounts.
EcrAmountholds a decimal, never adouble, and converts to the wire's minor units once — in the native host, with half-up rounding, so the two platforms cannot disagree about a rounding boundary. - Cancellable operations. The
start…methods return anEcrOperationwith acancel(), which answers the caller at once withEcrCancelled. Cancelling does not stop the terminal, and the outcome is unknown — documented as such everywhere it appears. - Transport awareness.
EcrTransportmirrors the TMS profile'secrMode.bluetoothandwebServiceanswer with a typedEcrUnsupportedfailure without opening a socket, identically on both platforms. - Android host bridging to
com.amwal-pay:ecr-sdk1.0.3, pinned. - iOS host bridging to
AmwalECR~> 0.1.0, from CocoaPods trunk or Swift Package Manager. Both hosts are bridges: no wire protocol lives in this package, and a native module in the same app can drive the same terminal through the same SDK without Flutter. - Both CocoaPods and Swift Package Manager on iOS, from the same sources —
ios/amwal_ecr.podspecandios/amwal_ecr/Package.swift, naming the same dependency range. - Contract tests on all three sides. Every method name, argument key, result key, outcome, failure kind and error code is asserted against frozen literals in Dart, Kotlin and Swift — the only thing keeping three hand-written copies of the same document in step.
- An example till demonstrating every operation, and in particular an outcome card that offers Ask the terminal and never Retry when the outcome is unknown.
Compatibility #
| Dart | ^3.5.0 |
| Flutter | >=3.22.0 |
| Android | API 21+, com.amwal-pay:ecr-sdk:1.0.3 |
| iOS | 12.0+, Swift 5.5, AmwalECR ~> 0.1.0 |
| ECR protocol | version 1 |