Case study · Business systems
Cafe ordering and loyalty platform with mobile app and admin
A cafe platform with a mobile app for ordering and rewards, plus an admin dashboard and API, built around a safe card-payment flow.
At a glance
- Who it's for
- Cafe customers who order and collect, and cafe staff and owners who run the menu, orders and rewards.
- Core stack
- Expo and React Native, Node.js and Express, MongoDB, Stripe
- What sets it apart
- The server, not the phone, decides what an order costs, and in the app's card checkout a payment can only ever produce one order, even if the app closes mid-checkout.
What it looks like in use
The customer's cart at checkout, and the staff view of the same order moving through the kitchen.
Large, oat milk$5.50
Quantity 2$4.00
25 stars, code applied$0.00
25 starsAvailable
Share your referral code+5 stars
From menu to pickup
Sam browses the menu on the phone
Sam picks a branch, finds a drink, chooses the size and milk, and adds it to the cart. A coupon or a free reward can go in too.
Paying is checked on the server
The app asks the server to set up the payment. The server works out the real price from its own menu, never from what the phone says.
The order arrives even if the app closes
Sam pays by card. In the app's card checkout, the order is created once the payment is confirmed, whether the app reports it or the payment provider does.
The kitchen moves it step by step
Staff see the order in the admin dashboard and move it from pending to preparing to ready. Sam's app shows the same steps.
Sam earns stars towards a free drink
Each paid order adds stars to Sam's balance. Stars can be spent on a reward, and a refund takes them back.
The challenge
A cafe wants customers to order and pay from their phone, and staff to see each order in a simple queue. It also wants a way to bring people back, such as points towards a free drink, plus coupons and offers it can switch on and off itself.
The hard part is the money. A phone can be closed, lose signal or be tampered with at any moment, yet one payment must always produce exactly one order, at the price the cafe set. Rewards add a second ledger that must stay correct through double taps, refunds and referral bonuses.
The solution
The platform has two parts. A customer mobile app lets people pick a branch, browse the menu, customise a drink, apply a coupon or reward, pay by card and follow the order. An admin dashboard and server let staff manage branches, menu, orders, coupons, rewards and the home-screen offer banner, and refund a payment.
The server is the single source of truth. It recalculates every price and tax amount itself, creates an order in the app's card checkout only after the payment provider confirms payment, and records stars in an audited ledger so every balance change can be explained.
For your technical teamhow it works under the hood
Mechanism: one payment, one order
Key features
What the system does, as built in the codebase.
- 01
Customer mobile app
Tabs for home, cart, rewards, account and event booking (which opens the cafe's web booking page), plus product detail, order tracking and order history. The sign-in session is kept in the device's secure storage.
- 02
Customisable menu
Branches, categories and products with sizes, milk options, a sugar toggle and one optional extra, each with its own price add-on. Items can be marked unavailable with a short reason shown in the app.
- 03
Safe card checkout
Card payments through Stripe's payment sheet. The server re-prices the cart, adds tax and, in this flow, creates the order only after payment is confirmed.
- 04
Order workflow
Pending, preparing, ready and completed, one step at a time, or cancelled with a required reason. Every change records who made it and when. Order emails go to the cafe and the customer when mail is configured.
- 05
Rewards and referrals
Stars earned on food spend, a signup bonus and a referral bonus, a reward catalogue where free-item rewards can be applied at checkout (fixed-discount rewards are defined but not yet applied at checkout), redemption codes, and per-reward limits.
- 06
Coupons and offers
Percentage or fixed-amount coupons with a minimum order, a use limit and an expiry date, plus an offer banner carousel on the app's home screen.
- 07
Admin dashboard
A browser dashboard for overview figures, branches, categories, products with image upload, orders, rewards and the offer banner, with a switch to pause online checkout.
- 08
Refunds that fix the stars
Full or partial refunds from the dashboard through Stripe, with the matching stars taken back from the customer's balance.
How it fits together
From a tap in the app to a confirmed order and a star balance.
- Mobile appExpo, React Native, payment sheet
- Admin APIExpress, JWT, validation, rate limits
- Stripepayment intents, refunds, signed webhooks
- MongoDBorders, menu, stars ledger
- Admin dashboardstatic page served by the API
- A payment-provider interface sits in front of Stripe, so the checkout code does not call Stripe directly.
- The app tracks an order by polling the API and can show a local notification when it is ready.
Implementation notes
Engineering decisions worth knowing about.
Server-side pricing
The server validates the cart, coupon and reward against the database and computes the charged amount. The app's tax figure is labelled as an estimate and mirrors the server's rule.
Idempotent order creation
An order id is pre-allocated when the payment is set up, and a unique sparse index on the payment intent id backs it, so the confirm call and the webhook can race safely. The server re-verifies the payment with Stripe rather than trusting the caller.
Webhooks that fail closed
Webhooks use the raw request body and verify the signature, and are rejected when no webhook secret is set. Processed events are de-duplicated with a unique key.
Audited stars ledger
Every award or deduction passes through one function keyed by a unique reference, so repeats are harmless, and a failed deduction is rolled back. Spending is a guarded atomic decrement that cannot go negative, and a refund reversal is the one case allowed to. A repair routine back-fills a missing signup or referral bonus.
Redemption safety
Redeeming uses a client request id so a double tap replays the same result, claims global and per-user limits atomically and undoes those claims on failure. Codes are stamped with a 30-day expiry, but enforcing expiry is listed as a deferred gap.
Order status machine
Transitions are limited to one step or a cancel, applied with optimistic concurrency so a double click cannot record twice. Older status names are normalised on read instead of rewritten.
Account and request hardening
Accounts lock for 15 minutes after five failed sign-ins. Separate rate limits cover sign-in, payments, coupons, redemptions and admin changes. Helmet headers, query sanitising and a 50 kb body limit are on, and a guard refuses to start a non-production run against the production database host.
Known gaps
An older order endpoint still creates an unpaid order with no payment step; it is kept for other callers and the security tests use it. A fully discounted order under $0.50 cannot go through card checkout, so a cart holding only a free reward is blocked. Apple Pay and Google Pay are accepted but their merchant IDs are placeholders. If a coupon or reward claim fails after payment, the order is still created and only flagged. Deferred per the launch checklist: redemption expiry enforcement, saved payment methods, a remote kill-switch for mobile checkout and richer reward analytics. Tax is one fixed rate and currency is US dollars.
Scripts and tests
Seed and backfill scripts load the menu, an admin and the starting reward. Four test scripts cover security, order status, rewards and Stripe payment flows against a running server and a development database; there is no automated runner.
Technology stack
Taken from the project's dependency and config files.
- Mobile app
- Expo SDK 54
- React Native
- React
- Expo Router
- TypeScript
- Stripe React Native SDK
- Expo Secure Store
- Expo Notifications
- Reanimated
- API
- Node.js 20
- Express
- TypeScript
- Mongoose
- Zod
- jsonwebtoken
- bcryptjs
- Multer
- Nodemailer
- Helmet
- express-rate-limit
- Admin UI
- Static HTML, CSS and JavaScript
- Data
- MongoDB
- Payments
- Stripe
- Delivery
- Docker
- AWS ECR
- AWS App Runner
- Expo EAS Build
For your technical teamtechnology layers
Technology stack by layer
Business value
What the system enables. No numbers are claimed.
- Lets customers order and pay ahead from their phone while staff work from one clear queue.
- Keeps prices under the cafe's control, because the server decides what is charged.
- Avoids duplicate or lost orders when a phone drops out mid-payment.
- Gives the cafe its own loyalty scheme, coupons and offers without a third-party tool.
- Lets staff pause online checkout, mark items unavailable and refund a payment from one dashboard.
Need a system like this built?
Tell us what it needs to do and we will scope it with you on a call.