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.

Sector
Cafe and food service
Category
Business systems
Maturity
Code-complete on a feature branch; live card payments await payment-provider keys; a legacy unpaid order endpoint remains; see known gaps

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.

Cafe ordering: Harbour Street branch
Harbour Street branchCheckout open Order #1042
Customer cart
Spiced tea latte
Large, oat milk
$5.50
Almond biscuit
Quantity 2
$4.00
Free drink reward
25 stars, code applied
$0.00
Coupon WELCOME10-$0.95
Tax$1.05
Total$9.60
Pay by card
Staff order board
#1040 Two flat whitesReady
#1041 Iced tea, muffinPreparing
#1042 Spiced tea latte and 2 morePending
One step forward or back, or cancel with a reasonGuided
Rewards
Stars balance32
Free drink
25 stars
Available
Invite a friend
Share your referral code
+5 stars
Store controls
Pause online checkoutOff
Refund through the payment providerAdmin only
Offer banner on the home screenScheduled
Illustrative screen sample items, prices and codes, not client data

From menu to pickup

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

Card checkout converging on one orderSequence diagram with four participants: mobile app, admin API, Stripe and MongoDB. Step 1: the app asks the API to create a payment, sending the cart and a unique attempt id. Step 2: the API re-prices the cart from the database and saves a pending checkout with a pre-allocated order id. Step 3: the API creates a Stripe payment intent for the server-side amount. Step 4: the customer pays in the payment sheet, talking to Stripe directly. Step 5: the app asks the API to confirm, and the API re-checks the payment with Stripe. Step 6: Stripe also sends a signed webhook to the API, which runs the same finalize step. Step 7: the finalize step creates the order exactly once, awards stars and sends emails.Mobile appAdmin APIStripeMongoDBCreate payment request:cart and attempt id1Re-price from the database,save pending checkout2Create payment intent forthe server-side amount3Customer pays in thepayment sheet4Confirm: re-check thepayment with Stripe5Signed webhook:same finalize step6Create the order once,award stars, send emails7
Read top to bottom. In the app's card checkout, the order does not exist until payment is confirmed. The app's confirm call and Stripe's signed webhook both run the same idempotent finalize step, backed by an order id allocated in advance and a unique index on the payment intent id, so whichever arrives first creates the order and the other finds it.

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.

  1. Mobile appExpo, React Native, payment sheet
  2. Admin APIExpress, JWT, validation, rate limits
  3. Stripepayment intents, refunds, signed webhooks
  4. MongoDBorders, menu, stars ledger
  5. Admin dashboardstatic page served by the API
The mobile app and the admin dashboard both call one Express API. The API reads and writes MongoDB and talks to Stripe for payments, refunds and webhooks. The dashboard is a static page that the API itself serves.
  • 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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

Cafe platform technology stack by layerLayered stack diagram. Mobile app: Expo SDK 54, React Native, React, Expo Router, TypeScript, Stripe React Native SDK, Expo Secure Store, Expo Notifications and Reanimated. API: Node.js 20, Express, TypeScript, Mongoose, Zod, jsonwebtoken, bcryptjs, Multer, Nodemailer, Helmet and express-rate-limit. Admin UI: static HTML, CSS and JavaScript. Data: MongoDB. Payments: Stripe. Delivery: Docker, AWS ECR, AWS App Runner and Expo EAS Build.Mobile appExpo SDK 54React NativeReactExpo RouterTypeScriptStripe React Native SDKExpo Secure StoreExpo NotificationsReanimatedAPINode.js 20ExpressTypeScriptMongooseZodjsonwebtokenbcryptjsMulterNodemailerHelmetexpress-rate-limitAdmin UIStatic HTML, CSS and JavaScriptDataMongoDBPaymentsStripeDeliveryDockerAWS ECRAWS App RunnerExpo EAS Build
The phone app and the browser dashboard both talk to the Express API, which stores data in MongoDB and delegates payments to Stripe. The API ships as a Docker image to AWS App Runner, and the app is built with Expo's build service.

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.

Discuss a build