Skip to content
Selected work

POS · Platform Channels · Zedcrest Group

Flutter on the screen, a vendor’s card kernel on the reader

Card-present payments on Android POS terminals. The UI is Flutter. The EMV kernel, PIN pad and printer live in a vendor Java SDK that will never be Dart. One channel contract between them, and no room to be wrong about either side.

Role
Mobile Engineer
Organisation
Zedcrest Group
Period
2024 - 2025
Surface
Flutter
01

The problem

Taking a physical card means driving hardware: card detection, an EMV kernel, a PIN pad, online authorisation, and a thermal printer. All of that ships as a callback-driven Java/Kotlin SDK that only exists on the native side.

Meanwhile the entire product surface, the amount entry, the receipt, the retry, is Flutter. The gap between those two worlds is where card-present bugs live.

02

Constraints

  • A single tap is a long-running, multi-stage process. Thirty seconds is normal: detect, read, PIN, authorise, print.
  • Terminal hardware and SDK builds vary by vendor. The same flow has to survive being pointed at a different box.
  • Android will reclaim the activity if the transaction takes long enough and the device is under pressure.
  • A failure part-way can leave the terminal in a state the next transaction inherits.
03

Decisions

01

Model the SDK as an event stream, not a request/response

A MethodChannel carries commands down. An EventChannel carries the terminal’s own timeline up: card presented, PIN entered, host authorising, printing. Flutter renders that timeline; it never guesses at it.

What I rejected

A single awaitable charge() method. A beautiful signature that hides thirty seconds of state, so the operator gets a spinner and no idea whether it’s safe to remove the card, which is the moment most failed transactions are actually created.

02

Keep the transaction’s source of truth native

Dart holds a projection of the transaction, not the transaction. If the isolate is killed or the app is backgrounded mid-authorisation, the native side still knows whether money moved, and the UI re-derives from it on resume.

What I rejected

Owning transaction state in Dart because that’s where the rest of the app’s state lives. Consistent, convenient, and it loses the answer to “did this card get charged?” the first time Android reclaims the activity.

03

One thin, versioned channel contract

Serialised domain types cross the channel, never SDK objects, never SDK-shaped maps. The vendor’s vocabulary stops at the platform boundary.

What I rejected

Passing the SDK’s own payloads straight through to Dart. Quicker to build, and it welds the product to one vendor’s field names, so the second terminal model becomes a rewrite instead of an adapter.

04

Where it landed

  • Card-present payment flows running across terminals from a Flutter UI, driving the native card SDK through a single audited channel surface.
  • The channel contract, not the SDK, is what the rest of the app depends on, so adding a terminal is an adapter, not a migration.
  • Flutter
  • Dart
  • Kotlin
  • Java
  • MethodChannel
  • EventChannel
  • EMV card SDK

Working on something like this?

I’m open to senior mobile and full-stack roles, and I’m relocation-ready.

donaldamadi15@gmail.com