An order that is late is a problem. An order whose status nobody agrees on is a worse one. The client's aggregator had a customer app, a restaurant tablet and a rider app that each held their own idea of where an order had reached, and support spent its day reconciling the three. ELIVTECH rebuilt the platform around a single order state machine on Laravel, with React Native apps that render that state rather than infer it.
The client aggregates restaurants across several cities with its own delivery fleet. Its original build had grown one interface at a time, each with its own status logic, so a restaurant could mark food ready while the customer app still displayed the order as unconfirmed and the rider had not been notified. Menus were updated by phone. Serviceability was a static radius that ignored whether a kitchen was accepting orders. ELIVTECH rebuilt the system with one authoritative order lifecycle, a menu service restaurants maintain themselves, Elasticsearch-backed discovery that reflects live kitchen state, and React Native apps sharing a codebase across iOS and Android.
At a glance
The challenge
Three applications, three versions of the truth, and a support team paid to reconcile them.
Order state implemented three times
Customer app, restaurant tablet and rider app each carried their own status logic and polled at different intervals. A kitchen marking food ready did not reliably reach the other two, so the same order could be preparing, ready and unassigned simultaneously depending on which screen you looked at.
Menus maintained by phone call
Restaurants reported price changes and sold-out items to an operations team who entered them manually. Between the call and the update, customers ordered dishes the kitchen could not make, and every one of those became a refund conversation rather than a meal.
Serviceability as a fixed radius
Discovery listed any restaurant within a set distance regardless of whether the kitchen was open, accepting orders or already beyond its capacity. Customers browsed and ordered from restaurants that would reject them, and rejection arrived after payment.
Rider tooling that assumed connectivity
The rider app required a live connection for every action. In basements, lifts and dense buildings, marking a pickup or delivery simply failed, so riders retried at the next signal or called operations, and the customer's tracking view stalled at the last successful update.
Two mobile codebases drifting apart
Native iOS and Android builds were maintained by separate efforts. Features landed on one platform first and the other followed weeks later, so behaviour differed by device and every bug had to be diagnosed and fixed twice.
Delivery estimates with no basis
The promised time was a fixed figure applied to every order regardless of kitchen load, distance or time of day. It was routinely wrong in both directions, which trained customers to distrust it and gave riders no useful sequencing information.
Our solution
One order lifecycle every application renders, and mobile apps built to work when the network does not.
Order Lifecycle Design
Four weeks defining the order lifecycle as an explicit state machine with named states, permitted transitions and the actor allowed to trigger each one. Cancellation, rejection, partial availability and reassignment were modelled as first-class transitions rather than exceptions handled in support. Every later interface was built to render this model, never to maintain a parallel one.
Laravel Core and Event Distribution
The Laravel backend owns the state machine and rejects any transition not valid from the current state, which removed a whole class of race conditions between a restaurant accepting and a customer cancelling. Each committed transition emits an event that fans out over queues to push notifications and live socket channels, so all three applications learn about the change from the same source. Transitions are written to an append-only log rather than overwriting a status column, which means an order's full history is reconstructable and support can answer what happened without inference.
Restaurant Self-Service and Live Discovery
Restaurants manage menus, pricing, preparation times and item availability from their own dashboard, and mark themselves busy or closed directly. Those signals write into the Elasticsearch index that powers discovery, so a kitchen that pauses disappears from listings within seconds instead of continuing to take orders it cannot fulfil.
React Native Apps with an Offline Action Queue
Customer and rider apps share a React Native codebase across iOS and Android. Rider actions are written to a local queue with a client-generated identifier and replayed when connectivity returns, so a pickup recorded in a basement is not lost. The server treats those identifiers as idempotency keys, making a replayed action safe rather than duplicated. The queue preserves ordering per order, so a pickup recorded before a delivery cannot arrive reversed after a long outage, and the app surfaces pending actions to the rider so nothing sits silently unsent.
Technology stack
Results
Measured once all three applications were serving from the new order lifecycle.
Before and after: platform engineering measures
- Order status logic implemented separately in three applications
- Menu and price changes relayed by phone to an operations team
- Discovery listed restaurants by fixed radius regardless of state
- Rider actions failed outright without connectivity
- Separate native iOS and Android builds drifting apart
- A single fixed delivery estimate applied to every order
- Support reconciled conflicting statuses by phone
- One server-side state machine every interface renders
- Restaurants maintain menus, availability and prep times directly
- Kitchen state writes to the index and gates discovery within seconds
- Offline action queue with idempotent replay on reconnect
- One React Native codebase shipping to both platforms together
- Estimates derived from kitchen load, distance and time of day
- Every transition carries an audit record with actor and timestamp
Project timeline
Lifecycle Design and Research
Order state machine definition with permitted transitions and actors, restaurant and rider workflow observation, failure mode cataloguing from support records, and the target Laravel and React Native architecture.
Backend Core and Event Distribution
Laravel state machine with transition guards, MySQL schema with append-only transition audit, queue-backed event fan-out, socket channels and push delivery, idempotency key handling, and the deployment pipeline on containerised AWS infrastructure.
Restaurant Dashboard and Discovery
Self-service menu, pricing, availability and preparation time management, busy and closed controls, Elasticsearch index design with live kitchen state, geo-filtered discovery, and estimate calculation from load, distance and time of day.
Customer and Rider Applications
React Native build for both platforms, live order tracking rendered from server state, rider offline action queue with replay, route guidance and proof-of-delivery capture, and accessibility work on the ordering and tracking journeys.
Field Testing and City Rollout
Automated end-to-end tests covering every lifecycle transition, deliberate connectivity-loss testing with riders in the field, load testing on dinner-peak profiles, monitoring on transition latency and queue depth, then a city-by-city rollout.
Key takeaways
What shaped the engineering decisions
- One state machine, rendered everywhere: Letting each client hold its own status logic was the root cause of the reconciliation work. Making the server the only authority on state, with transitions it can refuse, eliminated the disagreement rather than improving the sync between three versions of it.
- Idempotency keys make offline safe: Retrying a queued rider action without a client-generated identifier risks double-marking a delivery. Treating that identifier as an idempotency key means a replayed action is recognised as the same action, so the queue can retry freely.
- Let the kitchen gate discovery: A fixed serviceability radius was rejected because availability is a live property, not a geographic one. Writing busy and closed signals into the search index means a paused kitchen leaves the listings instead of accepting orders it will reject.
- Give restaurants the controls: Routing menu changes through an operations team guaranteed a lag between reality and the catalogue. Self-service removed the intermediary and made the party with the information responsible for it.
- Record transitions, do not overwrite status: An append-only transition log with actor and timestamp turned disputes into a lookup. It also made estimate calculation possible, because the data on how long each stage actually takes was finally being retained.
- Test the network failing, not only the network working: Connectivity loss was scripted into the test plan and rehearsed with riders in real buildings, because the offline queue only earns trust if it has been proven against the conditions that motivated it.
Want results like these?
Let's discuss how ELIVTECH can drive measurable outcomes for your business.
Start your project