Two clients, one API
These web apps stand in for the two mobile apps so the mobile team can see exactly what each screen sends and gets back. They talk to the same FastAPI backend as the dashboard (/api/v1, proxied to 127.0.0.1:8000), with the same tokens and rules. Every screen explains its workflow and shows a live log of its requests.
Expo app · devtranxit.apk
Driver app
A driver signs in, registers a vehicle, tops up the wallet and shows a toll QR at the lane.
- Username or email + password; JWT access token (15 min) + rotating refresh token (30 days)
- Wallet balance and ledger, top-ups (mock gateway in dev)
- Vehicles and RFID cards
- Signed toll QR (Ed25519, single use, 2 min)
- Trips: every pass, paid or declined
NEXGO N86 · TranxitLane
Lane POS
A terminal mounted in a CAVITEX booth reads the QR or card, charges the toll and opens the gate.
- Device sign-in with code + serial + secret
- Heartbeat every 30 s; the dashboard shows it online, picks up commands
- Verify → charge with a retry-safe
clientTxId - Offline mode: verify the QR signature locally, queue, sync later
- Today screen and technician install
One toll pass, end to end
- Driver app asks for a toll QR:
POST /driver/toll-qrwith the vehicle. The server signs a JWS (driver, vehicle, plate, class, a one-timejti, 2-minute expiry) and returns it with the current balance. - The driver holds the phone to the scanner. Here, Show to lane sends the QR to any open lane-POS tab in this browser (a BroadcastChannel); you can also copy and paste it.
- Lane POS calls
POST /pos/tolls/verify: no money moves. It answers READY with the plate, class, toll and balance, or a reason it can't pass (expired, used, low balance, plate mismatch…). - The POS charges:
POST /pos/tolls/chargewith a newclientTxId. If the network drops and it retries, the same id returns the same transaction, so the driver is never charged twice. PAID → gate OPEN, with a receipt number. - The wallet is debited in the same database transaction. The dashboard (port 3000) gets it at once over its live stream; the driver app sees it on its next refresh of trips and balance (the QR screen polls for it).
Try it
- Open Lane POS in one tab and pick a seeded terminal (dev only: read from
backend/.data/device-secrets.json). - Open Driver app in another tab. Sign in with a driver from
backend/.data/driver-accounts.jsonor create one. - Add a vehicle, top up, then open Pay toll and press Show to lane. The POS tab fills in the QR; press Verify, then Charge.
- Watch the gate result on the POS, the balance drop in the app, and the pass appear on the dashboard.
Where the state lives
| Data | Kept by | Here (dev) |
|---|---|---|
| Driver tokens | App secure storage (expo-secure-store) | localStorage of this origin |
| Device credentials + token | N86 keystore, provisioned at install | localStorage of this origin |
| Offline queue | N86 local database | localStorage, synced with /pos/tolls/sync |
| Balance, trips, rates, devices | Postgres behind the API: the only source of truth. Clients never compute balances. | |
Two tabs of the same app share one session because they share localStorage. Use a private window to run a second driver or a second lane.

