DEV Community

Akhil Baraik
Akhil Baraik

Posted on

What Building a Multi-Outlet Pizza Ordering App Taught Me About Location, Time Zones and Never Blocking the User

Food ordering apps look simple from the outside. You open the app, pick a pizza, pay, and wait for the doorbell. When my team at Crework Tech started work on the SUSI Pizza platform, I thought the hard parts would be payments and push notifications. They turned out to be the easy parts. The real work was in deciding which store serves a customer, what time it is for that store, and how to say "no" to a customer without making them close the app.

This post is a walk through the engineering decisions that mattered most. If you are building anything with multiple physical locations, delivery zones or opening hours, I hope some of it saves you a few late nights.

The setup

SUSI Pizza is a multi-outlet pizza and cake chain. Each outlet has its own delivery radius, its own opening hours and its own menu. Before the app, ordering was mostly manual and phone based. Our scope covered three pieces:

  1. A customer mobile app built in React Native, with Redux for state.
  2. A React admin panel for the product catalogue, per-store menus and pricing, banners, orders, store settings and staff roles.
  3. A Node.js and Express REST API on Firebase Firestore, with Firebase Auth (Google Sign-In plus mobile OTP), role-based access for super admins and store admins, and push notifications.

A separate team built the store app and the delivery-partner app. Both talk to the same backend, so our API had to be the single source of truth for every outlet. Payments run through Razorpay for UPI, cards and wallets, with Cash on Delivery as a fallback. Indian customers still like having that choice.

Problem 1: Which store serves this customer?

Given a customer's GPS position, the backend has to find the nearest store that is both in range and open right now. That sounds like one query. In practice it is three different answers that the app must treat differently:

  • A store covers you and it is open. Show the menu, allow checkout.
  • A store covers you but it is closed right now. Tell the customer exactly that, and when possible, when it opens.
  • No store covers your location at all. Say so honestly.

The early version collapsed the last two into one generic error. From the customer's side, "we don't deliver here" and "come back at 11" are very different messages. Splitting them made the app feel much more honest, and it also gave the client clearer data about where demand was coming from.

We also found a subtle bug in the fallback path. When the customer's location could not be resolved cleanly, one branch of code quietly used a store's own coordinates in place of the customer's. Distance from a store to itself is zero, so an out-of-range store could be labelled as deliverable. Nothing crashed. The numbers just looked plausible. The fix was simple once we saw it: never substitute a store location for a customer location, and treat "location unknown" as its own state rather than borrowing a value from somewhere else.

My takeaway: when a function can return "no", make it say why. An enum of reasons is far easier to debug and to design UI around than a boolean.

Problem 2: What time is it, really?

Every store has opening hours in Indian Standard Time. Servers and cloud functions often do not run in IST. If you write new Date().getHours() on a server set to UTC, your "open at 11 AM" check is off by five and a half hours, and the half hour makes it even easier to miss in testing.

This bug class came back more than once in different corners of the code, so we stopped fixing it case by case. Store hours logic now always converts to the IST time zone explicitly before comparing, rather than trusting the machine's local time. If you are in India and deploy to any global cloud region, I would make this a rule from day one. Also test the edges: just before opening, just after closing, and stores that close after midnight.

Problem 3: Products are not one size fits all

Pizzas have sizes. Cakes have weights. Drinks have serving counts. Some items have spice levels. Hardcoding a "size" field would have broken the moment the client added a new category.

We built an admin-configurable variant type system. An admin defines a variant type such as Size, Weight, Serving, Volume or Spice Level, and each product can have options under it. Every option carries its own price, discount price, stock and SKU. The customer app reads this generically, so product cards and the cart render correctly without new app releases for every new kind of product.

The lesson here is the usual one, but it is worth repeating: if the client will keep adding things, make the shape configurable, not just the values.

Problem 4: Copying menus across stores

With multiple outlets each carrying dozens of products, assigning and pricing items store by store was slow, repetitive work for the admin team. We added a "copy from another store" option right inside the assign flow, and a bulk tool that clones an entire menu with full pricing to another store. In one case that meant cloning 90 products in a single action instead of entering them one by one.

Admin tooling rarely makes it into launch announcements, but it decides whether the client actually keeps their data up to date. If updating the menu is painful, the menu goes stale, and then the customer app is wrong no matter how good the code is.

Problem 5: Never block browsing

This became a product principle across the whole app. A closed store, an out-of-range address or an unresolved location should never fully lock the customer out. They can always browse the menu and add items to the cart. Only checkout is gated on real delivery eligibility.

It sounds obvious, but it touched a lot of code: the store selection flow, cart validation, and the order calculation on the server. The server always re-validates price and eligibility at checkout, so the client app can be relaxed about browsing without ever trusting itself on money.

Promotions as rules, not campaigns

The client wanted offers like Buy X Get Y, combos, minimum order value discounts and free delivery above a threshold. Instead of writing code for each campaign, we built a rule-based promotion engine that evaluates whatever rules the admin configures. It also shows "locked" offers, so a customer can see what they need to add to unlock a deal. That one small UI choice turns a promotion from a hidden discount into a nudge.

The small things that add up

A few other items that took real time:

  • Rate limiting on auth endpoints, because OTP endpoints attract abuse.
  • React Native touch responder and scroll performance fixes, so long menus stay smooth on budget Android phones.
  • Live order tracking through Received, Preparing, Out for Delivery and Delivered, which stays visible as the customer moves around the app.
  • One-tap reorder from order history, since most food orders are repeats.

And one habit I now push on every project: for every location or pricing bug fix, we verified the result against live production data before closing it, instead of shipping on faith because the unit test passed.

What I would tell another team

If you are building a multi-location ordering product, here is my short list:

  1. Model "why not" explicitly. Out of range and closed are different states.
  2. Never trust server local time. Convert to the store's time zone every time.
  3. Make product variants configurable from the admin side.
  4. Invest in bulk admin tools early.
  5. Let users browse freely, and gate only the action that truly needs it.
  6. Validate prices on the server, always.

You can read the full SUSI Pizza case study on our site, including the platform breakdown and the customer journey. If you are a business in Jharkhand thinking about an app of your own, we also have a page on our mobile app development work in Ranchi.

I would love to hear how other teams handle store hours and delivery zones. Drop your approach in the comments.


Akhil Baraik is the founder of Crework Tech, an IT and digital marketing agency based in Ranchi, Jharkhand. Connect with him on LinkedIn.

Top comments (1)

Collapse
 
abhishek_goyal_c97dbf2eea profile image
Abhishek Goyal •

Problem 3 rings true. On an offline-first inspection app I worked on, every client had its own checklist, so we built a dynamic form builder that renders any inspection template, including ones with 500+ questions, rather than screens per client. Your variant types are the same move: the app learns the shape, the admin owns the values. Your "make it say why" point matched us too: one failed photo used to stall our whole upload queue, until each photo got its own failed state and a retry. Did variant types need versioning once old orders referenced options that changed?