Skip to main content
All case studies

Revenue-Generating Commerce Product

The Rollecito

A multi-location ordering platform for a working bakery: online catalogue, Stripe checkout, live order tracking and a real-time kitchen display.

Live · Commerce productRevenue-generating
Role
Product creator and Software Engineer
Period
2026 — present

Stages covered

  • Architecture
  • Build
  • Deploy
  • Operate
  • Monetise
app.therollecito.com
The Rollecito storefront on desktop, headline reading 'Freshly Baked Happiness in Every Roll' with Order Now and View Menu actions.
app.therollecito.com — customer storefront

Overview

The Rollecito is the ordering platform behind a cinnamon-roll bakery in Los Angeles. It is not a storefront template — it runs the business: customers browse a per-location menu, configure and pay for an order, and follow it by tracking code, while the kitchen works the same orders through a live queue and staff manage the catalogue, promotions and locations from a separate back office.

Order lifecycle states
5CREATED → PAID → PREPARING → READY → COMPLETED, enforced in the database
Interface languages
2English and Spanish, switchable in the header
Distinct role types
3Admin, manager and client, with separate staff sign-in

Problem

A small bakery selling a made-to-order product loses money in two places at once. At the front, orders arrive over phone calls and direct messages, get transcribed by hand, and go wrong — a missing option, a misheard quantity, a customer who arrives before anything is baked. At the back, the kitchen has no queue: it has whichever note is currently on the counter.

The generic fix is a third-party ordering page. It solves the form and leaves everything else: no kitchen view, no per-branch menu, a commission on every order, and a catalogue that cannot express 'cream cheese frosting, add strawberries, no drizzle' without turning every combination into a separate product.

Constraints

  • Pickup-only fulfilment, so the system has to be honest about timing rather than promising delivery windows it cannot keep
  • Mobile-first: most customers order from a phone, often while walking to the shop
  • Staff are not technical — the kitchen view has to be usable during service with no training
  • Multi-location from the start, because a menu that is not scoped to a branch is wrong the day a second branch opens
  • Payments must be reliable enough to be the shop's actual till, which means webhook confirmation rather than trusting the browser

Role and contribution

Product creator and Software Engineer

  • Designed the commerce domain: locations, catalogue, options, orders, payments and promotions
  • Built the customer storefront, the checkout flow and the order-tracking experience
  • Built the kitchen display and the staff back office, with a separate authentication path
  • Implemented Stripe payment intents and webhook-driven order confirmation
  • Wrote the MySQL schema, the stored-procedure layer and the migration tooling
  • Set up the containerised deployment and the approval-gated CI/CD pipeline

System design

Architecture

The Rollecito commerce topology

Two React clients — the customer storefront and the staff kitchen and admin back office — talk to a single Node and Express API served over HTTPS. Customers sign in through Firebase Authentication or order as guests; staff authenticate separately with a JWT session. The API keeps Socket.IO connections open to push order-status changes to both the customer's tracking screen and the kitchen queue. It reads and writes MySQL exclusively through stored procedures, stores product imagery in Amazon S3, and creates Stripe payment intents whose confirmation arrives back as a webhook.

Storefront
  • React
  • Vite
  • React Router
  • Redux Toolkit
  • i18n (English / Spanish)
Backend
  • Node.js
  • Express
  • Socket.IO
  • Helmet
  • Google reCAPTCHA
Data
  • MySQL 8
  • Third normal form
  • Stored-procedure data layer
  • Versioned migrations
Identity & payments
  • Firebase Authentication
  • JWT for staff sessions
  • Stripe payment intents
  • Stripe webhooks
Infrastructure
  • Docker
  • AWS Lightsail Container Service
  • AWS Amplify Hosting
  • Managed MySQL
  • Amazon S3
  • GitHub Actions with an approval gate

Customer journey

Ordering begins with a location, not a product. Choosing a branch scopes everything that follows: which items exist, which are available today, and where the order will be collected.

From there the customer configures an item through its option groups, adds it to a cart, and pays. Checkout does not require an account — guest ordering is a first-class path, because forcing a signup on a first-time customer buying a six-dollar pastry is how a bakery loses the sale.

After payment the customer gets a tracking code. The tracking screen is not a static receipt: it is subscribed to the same order events the kitchen produces, so the status moves as the kitchen works.

  • Choose location
  • Browse the location's catalogue
  • Configure item options
  • Cart and checkout, as guest or signed-in
  • Pay through Stripe
  • Track by code until ready for pickup

Catalogue and domain model

The catalogue is deliberately not a flat product list. Items belong to categories, carry option groups with their own option values, and are joined to locations, so the same item can exist in one branch and not another. Option groups can be cloned between items, because a bakery's toppings and frostings repeat across most of the menu and re-entering them by hand is how a catalogue drifts out of sync.

Orders reference the configured item and its selected options rather than a pre-computed product string, so the order total can be recalculated server-side at any point and a historical order still explains itself.

Order lifecycle

An order moves through five states — CREATED, PAID, PREPARING, READY, COMPLETED — and the transitions are enforced in the database rather than in application code. An order cannot skip a state or move backwards.

That placement is intentional. The same order is touched by the storefront, the webhook handler and the kitchen display, sometimes within the same second. If the rule lives in one of those callers it holds only for that caller; in the database it holds for all of them, including a future one that has not been written yet.

Kitchen operations

The kitchen view is a live first-in-first-out queue rather than a dashboard. Paid orders appear without a refresh, staff advance them through preparation and ready, and an order can be given priority or cancelled when reality requires it.

The same state change that redraws the kitchen queue also pushes to the customer's tracking screen. One event, two audiences — which is the reason the real-time layer exists at all.

Administrative workflow

Staff sign in through a separate authentication path from customers, and the back office is organised by role: menu and option management, locations, promotions, client records, order history, and revenue reporting.

The promotion engine has a preview step, so a discount rule can be evaluated against an order before it is published — a cheap safeguard against a promotion that turns out to be more generous than intended once it meets a real cart.

Deployment and operations

The backend ships as a Docker image to an AWS Lightsail Container Service; the storefront is built and hosted on AWS Amplify. Deployment is driven by GitHub Actions on merge to the production branch, behind a required manual approval — a deliberate brake on a system that takes real payments.

The API sits behind a security-header baseline and a strict cross-origin allowlist, with reCAPTCHA on the paths that would otherwise be attractive to automated abuse.

Monetisation

Revenue is direct: customers pay for their order through Stripe and the bakery keeps the transaction rather than a share of it after a marketplace commission. Owning the checkout also means owning the customer relationship, the catalogue and the pricing — including promotions, which are a first-class part of the system rather than something applied manually at the counter.

Key decisions and trade-offs

Decision

Put all database access behind stored procedures

Context
The system takes payments and is maintained by one engineer. Order state is written by the storefront, a Stripe webhook and the kitchen display.
Alternatives considered
  • An ORM with business rules in the service layer
  • Hand-written SQL in the controllers
Chosen approach
A MySQL schema in third normal form where the application's database user holds only EXECUTE privileges, with every read and write going through a stored procedure.
Reason
It puts the invariants at the last layer before the data. A bug in a controller, or a second caller added later, cannot write an order into an impossible state.
Trade-off
Business logic lives in SQL, which is harder to test and version than application code, and it slows down changes that touch the schema. Migrations and a reset script exist specifically to make that cost bearable.
Outcome
Order state transitions are guaranteed at the data layer regardless of which of the three callers is writing.

Decision

Confirm payment from the Stripe webhook, not from the browser

Context
The browser knows when a payment succeeded, and using that signal is the shortest path to a working checkout.
Alternative considered
  • Mark the order paid on the client's success callback
Chosen approach
The client creates a payment intent; the order becomes `PAID` only when Stripe's webhook confirms it, and payment records are tracked idempotently.
Reason
A customer closing the tab, losing signal, or double-submitting must not decide whether the bakery starts baking. The webhook is the only source that is both authoritative and delivered independently of the customer's device.
Trade-off
A brief window where the customer has paid and the screen has not caught up yet, plus webhook infrastructure to secure and monitor. The real-time channel is what makes that window short enough not to matter.

Decision

Scope the catalogue to a location from the first version

Context
The bakery currently operates one branch, and a single global menu would have been faster to build.
Alternative considered
  • One global menu, with multi-location added later when a second branch opens
Chosen approach
Locations are a first-class entity; items are joined to locations and the customer picks a branch before browsing.
Reason
Retrofitting a location dimension onto a live catalogue means migrating orders, menus and availability that already exist in production. Building it in while the data is small costs a table join.
Trade-off
An extra step at the start of the customer journey today, in exchange for not rewriting the ordering flow the day a second branch opens.

Decision

Allow guest checkout instead of requiring an account

Context
Accounts make order history, notifications and repeat purchases easier, and Firebase Authentication was already integrated.
Alternative considered
  • Require sign-up before checkout
Chosen approach
Guest checkout as a first-class path, with authenticated accounts available for customers who want history.
Reason
The friction of a signup is measured against a single low-value pastry order. Most first-time customers will abandon rather than register.
Trade-off
Guest orders have a weaker identity, which is why tracking is by code and why the system cannot assume a durable customer record on every order.

The hardest part: one order, three writers

The same order row is written by three independent actors. The customer creates it and may retry. Stripe confirms it asynchronously, possibly delivering the same webhook more than once. The kitchen advances it while both of those are still in flight.

Any two of them can arrive in the wrong order or twice. A webhook that lands after a staff member has already started preparing must not push the order back to PAID; a duplicate delivery must not double-record a payment; a customer retrying checkout must not create a second live order for the same intent.

The answer was to stop treating correctness as something each caller is responsible for. Payment records are idempotent so a repeated webhook is a no-op, and the state machine is enforced by the database so an out-of-order write is rejected rather than merely unlikely. That is the reasoning behind the stored-procedure layer: it is the only place all three writers necessarily pass through.

app.therollecito.com
Product grid on The Rollecito showing four cinnamon roll varieties with photographs, descriptions, prices and an Order button on each.
Catalogue items with per-item options and pricing
app.therollecito.com
The Rollecito location selection screen, prompting the customer to choose where they would like to pick up their order.
Ordering starts by choosing a location — the menu is per-branch
The Rollecito product listing on a mobile viewport with stacked product cards.
Mobile viewport — where most bakery ordering happens

Result

  • Publicly deployed at app.therollecito.com and used as the bakery's ordering channel
  • Phone and message ordering replaced by a self-service flow with server-calculated totals
  • Kitchen works from a live queue instead of hand-written notes, driven by the same events customers see
  • Payments taken directly through Stripe, with no marketplace commission on the order
  • Catalogue, pricing and promotions managed by staff without a developer in the loop

Lessons learned

  • In commerce, the authoritative event is the one that does not come from the customer's browser. Designing around the webhook first avoided a class of bugs rather than fixing them later.
  • Enforcing the state machine at the data layer paid for itself as soon as a third writer existed. Rules in application code only bind the caller that remembers them.
  • Multi-location was much cheaper to build before there was production data than it would have been after.
  • The kitchen display was the feature that changed how the business actually runs; the storefront was the part that was visible.

Next improvements

  • Delivery fulfilment alongside pickup, which adds address handling, dispatch and a second timing model
  • Automated tests around the payment and state-transition paths, currently the least covered area
  • Structured operational monitoring and alerting on webhook failures
  • Moving stored-procedure logic under the same review and testing discipline as the application code