A production loyalty and store-operations platform built for a real 12-store gas station chain, examined role by role, security layer by security layer.
Lucky Stop is a Texas gas-station chain. Their loyalty app is free for customers, earns cashback on every purchase, and gives stores a full operations layer underneath it: staff scheduling, inventory requests, daily reporting, and store-to-store messaging, all gated by exactly who is allowed to see and do what.
The platform pays for itself the same way it pays the business: stores cover a monthly subscription plus a small platform fee on the cashback they issue. Customers never pay a cent.
DevAdmin and SuperAdmin work entirely from the admin web app. Store Manager, Employee, and Customer work entirely from the mobile app. Both talk to the same backend and the same role checks, so access rules can never drift between the two.
Enforced at the API, on every request, not just hidden in the UI.
requireRole(minRole): one generic rule per route. Any role ranked at or above the minimum passes automatically, no per-route allow-lists to keep in sync.
224+ routes gated this way across the API, every one declares only its minimum role. Adding a new role, or promoting a permission, never means hunting down a scattered allow-list. A store-scoped route adds a second check on top: not just "what role," but "which store."
Swipe to see every role →
| Feature | DevAdmin | SuperAdmin | Store Mgr | Employee | Customer |
|---|
A Store Manager or Employee can belong to more than one Lucky Stop location. A join table, UserStoreRole, links a user to every store they can access, and a single allStoresAccess flag lets one designated chain-wide manager bypass the check entirely. Access is looked up live from this table on every store-scoped request, so a change takes effect immediately, not at next login.
A multi-store manager's Scheduling and Chat pages once queried only storeIds[0], their first assigned store, so their other stores silently never loaded. Fixed once it was found, and swept for the same pattern across the rest of the codebase.
The parts a demo never shows, but a real business depends on.
A JWT signature alone was never enough. A deactivated or deleted account's token could keep working for up to 7 days, most visibly on iOS where Keychain survives a reinstall. Every authenticated request now re-checks the account is still active directly against the database.
No receipt photo, no points. Enforced server-side regardless of what the app's UI allows, and the same photo can never be reused across two transactions. A fraud engine hard-rejects duplicate customer/employee pairs within 2 minutes or purchases over $2,000, soft-flags unusual velocity to a manager, and routes anything over $800 to SuperAdmin.
Tiered cashback, category bonuses, and promotions can stack, so every grant's effective rate is hard-capped at 10% server-side, even if a rate gets misconfigured upstream.
A blocklist of common PINs, bcrypt hashing, account lockout after 5 failed attempts, and a 3-entry PIN history that blocks immediate reuse, enforced even when an admin resets a PIN on someone's behalf.
Deactivating a user, resetting a PIN, removing store access, deleting an offer: all written to an audit log with who, what, and when, surfaced to SuperAdmin as a live high-risk action feed.
A manager's assigned stores are looked up fresh from the database on every store-scoped request, so adding or removing a store from someone's access takes effect immediately, not after their next login.
An early build of the forgot-PIN flow returned the one-time verification code in the API response, and the admin login screen auto-filled it in dev mode. A focused security pass found and closed all three issues in one commit: the OTP no longer round-trips to the client at all, and recovery codes are delivered by email instead.
Real screenshots from the actual admin console, not wireframes.
12 stores, Texas.
Free for customers. Stores pay a monthly subscription plus a small platform fee on cashback issued.
Tiered cashback (5 tiers) plus per-category bonus rates, with a flat cents-per-gallon mode available for gas and diesel.
Points redeem for real product credits in-store, not a third-party gift card.
Rates, category bonuses, and a flat cents-per-gallon mode for gas and diesel are all configured live by SuperAdmin/DevAdmin, never hardcoded. The screenshot above is this exact table.
Serial (RS-232C, DTR/DSR handshake) link to Verifone and Topaz in-store receipt printers, so a customer can self-grant points straight from a printed receipt.
Android: signed release builds with R8/ProGuard minification. iOS: automated build, archive, export, and upload to App Store Connect.
GPS-based store detection automatically surfaces the correct gas/diesel prices for the customer's actual physical location, reusing the app's existing age-verification location gate.
Every grant carries a computed dev-cut / store-cost split, so the platform fee model is enforced at the transaction level, not just on a monthly invoice.
All 12 stores on the platform today, running real daily transaction volume
Shelf and price label printing, chain-wide catalog, store-scoped inventory requests
Location-aware fuel pricing and a hardware integration with in-store receipt printers
Customer dispute handling, staff scheduling, and manager-to-manager chat
Android release builds ship with R8/ProGuard minification; iOS ships through an automated build-and-upload pipeline
Cliff Industries