Skip to content
A teal line drawing on a dark grid: two outlined boxes on the left merge into one larger, faintly filled box; from it a single horizontal line runs right, passes through a small dot with a crosshair, and ends at three narrow bars stacked on top of one another.

“The bridge is the whole job.”

Passport chips and payment terminals, from React Native

Two engagements, two kinds of hardware, one approach: write the protocol natively, expose it once, and let the JavaScript side stay ignorant of both.

Get in touch
Two engagements · a German financial institution, 2020 · cross-border remittance, 2022 · both via agency
2 → 1
platforms, one protocol implementation
100+
terminals updated over the network
5 min
retry when an update fails
Summary

Twice we had to make React Native talk to hardware that had no library worth using: the NFC chip in a German passport, and a Chinese payment terminal running Android.

Both times the answer had the same shape — implement the protocol in the platform’s own language, expose a narrow surface to JavaScript, and keep the platform detail from leaking upward. What differs is what the hardware demanded.

The problem

React Native is excellent until you need something the platform does and JavaScript cannot see. Reading an e-passport is not a matter of calling an API. It is a protocol — ICAO 9303, with an authentication handshake before the chip will say anything — and in 2020 there was no usable library for it.

The constraint

The terminal was worse: a Chinese device whose SDK documentation was sparse and written in Spanish, which we translated in order to work with it at all. In both cases the bridge is the whole job — get the surface wrong and every future feature pays for it.

What we did

  1. The chip protocol in Java, the same in Swift

    Implemented for Android, then ported to iOS and exposed to React Native through a Turbo Module — then still an experimental part of the new architecture, requiring manual registration and with thin documentation.

    One protocol, two platforms
  2. The port matters more than the implementation

    Two independent implementations of a protocol like this drift: they start treating edge cases differently, and then the same passport passes on one phone and fails on another. In identity verification that is not a cosmetic inconsistency — it is unequal treatment of the person holding the document.

    Same passport, same outcome
  3. The terminal exposed, then locked down

    Card, printer and hardware exposed through a Turbo Module in Java. Then a launcher in Kotlin that replaced the Android home screen and locked the device to a single app. On a device that dispenses cash that is a control decision rather than a cosmetic one: the person behind the counter must not be able to open a browser, install anything, or change a setting.

    One app, no way out
  4. It paid for itself twice — updates over the network

    A device locked to one app you control can be updated remotely. Over a hundred terminals were updated without anyone visiting a site — the app checked object storage for a new version, and if the update failed nothing happened at all: it kept running and retried every five minutes until it succeeded. Every terminal was registered in an admin dashboard showing its activity from the moment it connected through to who issued which coupon.

    100+ terminals, zero site visits
  5. Thermal printing: the printer knows only dots

    A receipt printer does not know about images, fonts or layout — it takes dots. A logo has to become a bitmap and be positioned by hand, in paper units, with no preview. It sounds trivial until you try it, and it can only be done by printing and measuring.

    Print and measure
TWO KINDS OF HARDWARE, THREE DEMANDS
The passport a protocol, not an API

ICAO 9303, with an authentication handshake before the chip will say anything. Implemented in Java, ported to Swift, exposed through a Turbo Module. Decoding the retrieved data was an external service — ours was reading it off the chip.

The same shape of answer every time: the protocol in the platform’s language, a narrow surface toward JavaScript, and detail that does not leak upward.

The result

  • Cryptographically signed passport data read from the chip, on both platforms, from one protocol implementation
  • Over 100 payment terminals locked to a single app and updated over the network
  • Failed updates costing nothing: the terminal kept working and retried

What we’d do differently

On the passport module we ported the Java implementation to Swift by hand and verified it by testing. What we should have built was a shared set of protocol test vectors — known inputs and expected outputs — run against both implementations. Testing that they behave the same is not the same as testing that they are correct, and we did the first while believing we had done the second.

Related
Identity verification for German financeWhere the chip you read becomes proof about a person.Five problems and the system that answers themThe same principle: the platform split lives below the call site.
All 18 case studies
  • Java
  • Kotlin
  • Swift
  • Turbo Modules
  • React Native
  • Android
  • NFC / ICAO 9303

Have hardware JavaScript cannot see?

Get in touch

Systems that can’t stop — from architecture to production.

© 2026 Micro Tech, Sarajevo