Skip to content

Oyo State field systems

Digital infrastructure for field operations across Oyo State.

Role
Mobile Engineer
Context
AFT Solutions Limited, for Oyo State agencies
Timeline
Aug 2024 to present
Platforms
Android POS, Android, iOS
Status
Internal
Primary metric
₦6B+processed across systems
OYO-VIO running on a dedicated Android POS terminal with a physical keypad.

Overview

These are the tools Oyo State officers use on the road, at checkpoints and at hospital desks. Traffic officers issue tickets and rider ID cards on OYRTMA. Vehicle inspection officers ticket, verify documents and take payment on OYO-VIO. Haulage staff create trips, pass checkpoints, issue QR-verifiable waybills and sell vehicle documents on E-Haulage. MedicFlow bills patients in hospitals.

Most of them run on dedicated Android POS terminals from two vendors, Morefun and Accelerex. The same device takes the card payment and prints the ticket, receipt or ID card on the spot. Officers work outdoors, often on weak mobile networks, and the terminal in their hand is the whole system.

Across these systems, more than ₦6B in payments has been processed.

My role

I am the mobile engineer building the field applications in React Native. I implemented the apps, integrated the backend APIs, wrote the native bridges to each terminal vendor’s printing and payment services, handled the device-specific work (camera, printing, card payments) and manage builds and over-the-air updates to devices in the field. Backend services and the vendors’ own software are built by others.

The challenge

Dedicated POS hardware is not a normal phone. It can lack apps that phones take for granted, each vendor exposes printing and payment differently, printers impose their own limits, and once devices are deployed across a state you cannot collect them for every update.

  • Two terminal vendors with different SDKs and print rules.
  • Some terminals ship without a system camera app.
  • Printing and payment run in a separate vendor application.
  • Devices are spread across the state and hard to recall.

Architecture

  1. Field application

    React Native on the terminal

    Built by me

  2. Native bridge

    Config plugin and Android module per vendor

    Built by me

  3. Vendor print and payment service

    Morefun SDK or Accelerex app

    Third party

  4. Card reader and printer

    Terminal hardware

    Third party

The React Native code never talks to hardware directly. A small native module per vendor sits behind one typed printing and payment interface, set up by an Expo config plugin at prebuild, and POS features only appear when that module is present on the device.

In the field

OYO-VIO on a POS terminal showing "Receipt printed successfully", with two VIO payment receipts fed out of the printer. Plate numbers redacted.
The flow from the architecture above: the app asks the POS application to print, and the receipt comes out of the terminal.
A printed VIO payment receipt showing ticket number, amount of NGN 5,000 and date. Plate number redacted.
A VIO payment receipt, printed from an image the app wrote to shared storage.
The OYO-VIO Ticketing screen on a POS terminal, listing a paid ticket and one awaiting document verification.
Ticketing on OYO-VIO: each ticket carries its location, vehicle and status.

The systems

  • OYRTMA

    Road traffic enforcement for the Oyo State Road Traffic Management Authority: tickets, payments and printed rider ID cards.

  • OYO-VIO

    Vehicle inspection officers issue tickets, verify documents, take payment and print receipts on the terminal.

  • MedicFlow

    Patient registration, records and billing for hospitals, on the same POS terminals. It has its own case study.

  • E-Haulage

    Haulage and road tax: trips, checkpoints, QR-verifiable waybills and vehicle document sales, paid online or on the terminal.

  • Park n’ Pay

    Parking management and payments for vehicles and trucks.

  • OYO-BMS

    Business registration that assigns traders a unique business number.

  • OYO-SSS

    Waste management and whistleblowing: tickets, enforcement requests and fines.

Engineering challenges

A camera on a device with no camera app

problem
OYRTMA captures rider photos for ID cards. The flow worked on phones and older terminals but failed on every new terminal.
constraint
launchCameraAsync from expo-image-picker does not talk to the camera. It sends an ACTION_IMAGE_CAPTURE intent for another app to handle, and the new terminals have camera hardware but no camera app to answer it.
approach
I switched those terminals to expo-camera, rendering the preview inside the app with my own capture, preview and Retake / Use Photo actions. ImagePicker stays on phones and older terminals, and the app checks which device it is on.
result
Photo capture works on every terminal in use, with or without a system camera app.

Printing receipts through another application

problem
An earlier integration took base64 images, but the Accelerex printer’s Bitmap field expected a file path. Writing the image to the app’s cache and passing that path still crashed.
constraint
Printing is handed off to a separate app on the terminal, and it cannot read files inside the React Native app’s sandbox.
approach
Write the composed image to shared media storage with expo-media-library, pass that path to the POS app, and delete the temporary file once the print job completes.
result
Receipts print reliably from the payment application, without leaving images behind in storage.

An ID card that fits in 300 pixels

problem
Printed rider ID cards came out as small, unreadable blocks, however large the source image was.
constraint
The Accelerex printer scales every image to 300 pixels wide, so a bigger image prints smaller. Each print field holds one image and prints as its own pass.
approach
The card’s details print as native text lines, which the terminal renders at a legible size. Only the pictures go through the image field: the state logo and the rider’s photo are composed side by side into one 300 by 150 header, authored at its final print size.
result
ID cards print legibly in a single pass.

Two vendors, one codebase

problem
The same apps had to print and take payment on Morefun and Accelerex terminals.
constraint
Morefun ships a Java SDK whose real package names and method signatures differed from what the integration expected. Accelerex works through a separate app. Neither is part of Expo.
approach
A small native Android module for each vendor, found by inspecting the SDK itself where the docs were wrong, exposed to React Native through one typed interface and installed by an Expo config plugin. Devices without the module fall back gracefully.
result
The same React Native code prints and collects payment on both vendors’ terminals.

Confirming payments without polling

problem
After an online payment on E-Haulage, the trip should become ready without anyone refreshing the screen.
constraint
Field networks drop out, and a payment that never confirms cannot leave the screen spinning forever.
approach
The app opens an authenticated Server-Sent Events stream for the payment reference. A success event stops the pending state, releases the waybill and moves the officer on. Malformed events, connection errors, unmounting and a five-minute timeout are all handled.
result
Payment, trip status and QR-verifiable waybill update together, live.

Updating devices you cannot collect

problem
Every change meant building an APK, moving it to a terminal, installing it and testing, again and again.
constraint
Terminals are deployed with officers across the state, run on two CPU architectures, and have no Play Store.
approach
Terminals get builds that include expo-updates. eas.json has separate profiles for armeabi-v7a (older terminals) and arm64-v8a (newer ones), each with its own update channel, and JavaScript fixes ship as OTA updates.
result
JavaScript fixes reach terminals when the app next launches, with no rebuild or reinstall.

Outcome

  • ₦6B+

    processed across systems

  • 7

    production systems

  • 2

    POS terminal vendors supported

Most of the hard problems here were not in React. They were in Android intents, storage sandboxes, vendor SDKs and printer limits, and the fix each time was to understand the device instead of assuming it behaves like a phone.

Technologies

  • React Native
  • Expo
  • TypeScript
  • TanStack Query
  • Zustand
  • Expo config plugins
  • Android native modules
  • Server-Sent Events
  • EAS Update