Back to engineering notes
Sep 30, 202611 min

From Street Workflow to Mobile System: Parking Tickets, Offline Boundaries, and Bluetooth Receipts

How I approached a field-ready parking platform where ticket state, collections, Android packaging, and a 58mm printer all had to work as one dependable workflow.

LaravelReactCapacitorBluetooth LEPWA

The real starting point was the street workflow

A parking ticketing system can look simple when it is reduced to a form: enter a plate number, choose a rate, and save. The actual workflow is much wider. An agent has to identify a vehicle, select a parking zone, capture evidence, apply the correct rate, collect payment, issue a receipt, and later reconcile the day. Administrators need different controls for agents, rates, reports, and system-wide tickets. The system therefore had to connect field speed with office accountability instead of treating them as separate products.

I started by writing the lifecycle in operational language before thinking about screens. A ticket begins active, accumulates a duration under a selected rate, and eventually becomes a paid transaction with a receipt and payment method. Flat, hourly, and overnight rates create different calculation rules. Cash and digital payments create different reconciliation questions. This exercise turned a broad feature list into explicit state transitions that Laravel and MySQL could enforce.

Choosing a stack around one source of truth

The implementation uses Laravel 12, Fortify, MySQL, Inertia.js, React, and TypeScript. That combination kept authentication, validation, authorization, and transaction rules on the server while still giving agents a responsive application-like interface. Inertia was useful because it avoided maintaining a separate public API for every screen. Controllers could prepare role-specific page data, React could handle the interaction, and the same session and authorization rules remained in charge.

The important decision was not the framework list; it was ownership. The server owns ticket status, rate calculations, receipt identifiers, payment records, and collection totals. The client owns temporary interaction state such as the selected zone, form progress, printer selection, and feedback. Keeping that line clear reduces the risk that two devices calculate different answers or that a UI shortcut bypasses a financial rule.

  • Admin users manage agents, rates, global reports, and all tickets.
  • Agents create tickets, accept payment, print receipts, and review personal remittances.
  • Every completed payment remains part of a searchable transaction trail.

Modeling time and money before polishing screens

Duration-based parking is a source of edge cases. The display can update every minute, but the authoritative amount should be derived from timestamps and the persisted rate configuration, not from a JavaScript timer. A timer is presentation; entry time, exit time, rate type, and payment time are business facts. This distinction makes a refresh or temporarily suspended app much less dangerous because the amount can be reconstructed from stored data.

The same principle applies to collections. A dashboard total should be the result of recorded payments grouped by method and agent, not a number incremented in browser memory. Cash, GCash QR, and card payments need a common transaction shape even when their confirmation steps differ. Once payments are modeled consistently, date filters, CSV exports, agent breakdowns, monthly remittances, and privacy controls become views of the same ledger rather than isolated reporting code.

Moving from responsive web to an Android field tool

The agent experience had to work on phones, tablets, and desktops, but responsive CSS alone was not the end goal. Capacitor wrapped the React application for Android so the project could use device capabilities while preserving the Laravel and Inertia application model. Touch targets, short forms, camera access, connection feedback, and clear payment states matter more in the field than dense desktop navigation.

This also forced a more disciplined separation between platform-specific behavior and core ticket behavior. Creating or paying for a ticket should not depend on whether the page is running in a browser or an Android shell. Printer discovery and native permissions can live behind a device-facing adapter, while ticket and payment requests continue through the same server workflow. That boundary prevents the mobile build from becoming a second application that slowly drifts away from the web version.

Bluetooth printing was a workflow, not a button

The PT-210 thermal printer communicates through Bluetooth Low Energy and expects ESC/POS-style receipt commands. That introduces device discovery, permission handling, connection state, narrow paper formatting, encoding, and reconnection behavior. A successful payment does not guarantee a successful print, so those outcomes cannot be represented by one boolean. The financial transaction must remain valid even if the printer is unavailable, and the user needs a safe way to reconnect and print the same receipt again.

I treated the receipt number and stored transaction as the durable source. The print payload is generated from that record rather than from unsaved form state. This makes retry behavior understandable: retrying a print does not repeat a payment. The receipt layout also has to respect a 58mm paper width, use concise labels, and include the unique receipt or QR information without relying on a desktop preview that the printer cannot reproduce.

  • Expose scanning, connecting, connected, failed, and disconnected states.
  • Keep the selected printer as a device preference, not a financial record.
  • Allow receipt reprints from persisted payments without creating duplicates.

Defining the offline boundary honestly

The project includes PWA and service-worker support, but offline-ready should not be used as a vague promise that every transaction can safely happen without connectivity. Static assets and previously loaded screens can be cached relatively easily. Queuing a new ticket or payment is harder because identifiers, timestamps, rate changes, and duplicate submissions must be reconciled when the network returns.

The lesson is to define offline behavior operation by operation. The interface can explain that the shell is available while a server-confirmed action is pending or unavailable. If queued writes are introduced, they need client-generated idempotency keys, an outbox, visible sync states, conflict handling, and server-side duplicate protection. A silent retry loop is not enough for money-sensitive work. Clear limits preserve operator trust better than an offline badge that hides uncertainty.

What the system delivered

The payoff was not an abstract claim about transformation. It was a connected set of working capabilities: agents can create and filter tickets, calculate duration-based charges, accept multiple payment methods, issue QR-enabled receipts, print through a supported Bluetooth device, and review their collections. Administrators can manage users and rates, inspect all tickets, filter collection reports, export data, and compare cash with digital payments.

The broader learning was that field software succeeds when failure paths are designed alongside the happy path. A disconnected printer, a suspended Android app, a slow request, or a rate change should not corrupt the transaction model. Starting with lifecycle and ownership decisions made the later interface work simpler, because every status shown on screen had a durable meaning behind it.

Practical takeaways

For similar field applications, I would repeat the same order: map the physical process, define authoritative states, separate durable operations from device conveniences, and only then optimize the interface. Hardware integration should be treated as an unreliable boundary. Offline support should name exactly what remains safe. Reports should come from the transaction model rather than parallel counters.

That is the zero-to-hero progression I value most: not adding the most technology, but steadily removing ambiguity until an agent, administrator, server, and printer all agree about what happened.