Draft — for team verification Ring n Bring · 35 deployables
The whole platform hangs off one idea: an app saves an order into a shared database, and everything else wakes up and reacts to that save. Read the five boxes below and you have the gist. Then press Play and watch the full journey draw itself one hop at a time.
resturant/{id}/Orders/{orderId}. This save is the event.
That's it. Everything below is detail on box 4 and 5. The one thing to hold onto: there is no central order service that owns this — the database save is what coordinates everything, so the same order can be changed by about ten different pieces of software.
One order, from the guest's phone to the kitchen till
Six columns for the six things involved, time running downward. Follow the numbers and you have the whole journey; each box also carries the real call or file name in small type, so you can search for it later. The one step worth remembering is 4: saving the order is what sets everything after it in motion — nothing below step 4 is called by the API, it all reacts to that save. Full explanation of every step is underneath.
What actually happens at each step
Every table has its own printed code. Scanning it opens the ordering page in the phone's browser — there is nothing to install. The code carries which venue and which table, so the app already knows where the order is coming from before the guest taps anything.
The ordering app reads the venue's live menu and prices. Everything stays on the guest's phone until they press order — nothing is sent yet.
The app also decides when payment will happen: pay now, pay after staff confirm, or pay at the end of an open tab. That single choice sets the order's starting status and changes what steps 8 and 9 look like.
The cart is posted to the API, which asks three questions. Is this a real table at this venue? Is a tab already open on it — in which case the items join the existing order rather than starting a second one? And is this guest ordering unusually fast, which is how spam and double-taps get caught?
If any check fails, nothing is written and the guest sees an error. This is the last point where an order can be rejected cleanly.
Everything about the order — items, table, guest, totals, how it will be paid — is written as a single document in the shared database. Not a row here and a row there: one document that the whole platform reads and writes.
This save is the event. Nothing below this step is called by the API. The API's job ends here; the database noticing the new document is what starts everything else. If you remember one thing from this page, make it this.
The database notices a new order document and runs a small program automatically. Nobody calls it. It was registered once to listen for new orders, and it fires every time one appears.
That one program then does a long run of follow-on work — naming the order, messaging the guest, auto-confirming, pushing to staff devices, opening the till check, updating counters and analytics. Around thirty other programs listen the same way for other kinds of change.
The order is pushed to whatever point-of-sale system that venue runs, which is what makes it appear in front of the kitchen. Until this happens, nobody is cooking.
Every POS vendor speaks a different language, which is why there is a separate integration service per vendor rather than one shared one.
The guest gets a WhatsApp message with their order and a link to follow it. At the same time staff tablets and smartwatches get a push so someone in the venue knows an order has landed.
The app sends the guest to whichever payment provider that venue is configured for. They pay on the provider's own page — card details never touch our systems — and the provider then calls back to say whether it worked.
Which provider is used is a per-venue setting, which is why there are nine of them wired in.
The confirmation is not stored somewhere separate. It is written onto the very same document from step 4, so an order always carries its own payment history with it.
And because that is another write to the order, the loop starts again — which is the whole point of the next step.
A different program listens for changes to an existing order, so the payment write sets it off. It tells the till the check is paid, sends the guest a receipt by WhatsApp and email, updates the staff devices, and records the sale for analytics.
Same pattern as step 5, and it will happen again on every later change — status updates, staff edits, cancellations. This is why the same order can be touched by about ten different pieces of software.
Why it is built this way. Because everything hangs off the save rather than off the API, a new feature can
start reacting to orders without anyone changing the ordering API — you register another listener and it just
works. The cost is the other side of the same coin: no single service owns an order, so ten different
programs can change the same document, and that is exactly what the detailed map below is for.
Two simplifications above. Payment can also happen before the food is ordered, or at the end of an
open tab covering several rounds — the steps are the same, the order differs. And staff can place orders from a
tablet, phone or watch instead of the guest scanning, which joins this journey at step 3 through a different API.
Solid line the supported path Dashed line a shortcut that skips the API checks Rounded box app people use Heavy box database Dashed box outside system Click any box for ports and files
There isn't one status lifecycle — there are three, and the venue's payment setting picks which one applies
An order's onlinePaymentStrategy decides both where it starts and which transitions are legal.
This is the single most confusing thing about the platform: the same status word means different things depending
on the strategy, and pending exists in two of the three but not the middle one.
pending_paymentpending_payment→received pending canceledreceived→confirmed canceledpending→confirmed canceledconfirmed→donecanceled→pending_payment received pendingunpaidunpaid→pending_payment canceledpending_payment→confirmed canceledconfirmed→donecanceled→pending_payment unpaidreceivedreceived→confirmed canceledpending→confirmed canceledfailed→confirmed canceledconfirmed→donecanceled→pending failed receivedcanceled back to an open state — that's
deliberate, so a cancelled order can be reopened rather than recreated.done can't be reached normally. canOrderTransition refuses any move to
done unless the caller passes canTransitionToDone explicitly. In practice orders are
marked done in bulk when the tab closes, not one at a time.POST /api/resto/order/update on LogisticsAPI. The
receiver tablet, the admin dashboard, the Cloud Functions, ingestion and order-service all write
status straight to Firestore and never consult this table.The vocabulary that carries most of the confusion
orderSession — several orders on one table, settled together. Closing it marks every confirmed order in it done.newOrderAdded as a sub-collection.onlinePaymentStrategy — whether the guest pays first, after staff confirm, or at the end. It picks the status lifecycle above.The same service answers to a different name in every environment
Read out of bitbucket-pipelines.yml. Dev and QA follow a clean
<env>.<service>.qrbring.com pattern; production does not follow any pattern at all. Two
services are restarted under different pm2 names depending on the environment — flagged below, because a
restart aimed at the wrong name silently does nothing.
| Service | Port | Dev host | QA host | Production | pm2 name |
|---|---|---|---|---|---|
| MicroMenuAPI | 7070 | dev.mm | qa.mm | apiprod.qrbring.com | micromenu |
| LogisticsAPI | 7079 | dev.logistics | qa.logistics | logisticsapiprod.qrbring.com | logistics |
| SocketAPI | 5545 | dev.socket | qa.socket | socketsapiprod.qrbring.com | sockets |
| integrationapi | 7678 | dev.integration | qa.integration | integrationapi.qrbring.com | integration-api |
| PayTabIntegration | 7700 | — | — | — | — |
| urwayintegration | 7699 | — | qa.urway | urway.integration.qrbring.com | urway / urway-api |
| ccavuenepaymentapi | — | dev.ccavenue | qa.ccavenue | /var/www/ccavenue | ccavenue |
| stripe-integration | 7555 | — | — | /var/www/stripe-integration | stripe / stripe-integration |
| rnb-notifications | — | dev.notifications | qa.notifications | notifications.qrbring.com | rnb-notifications |
| crm-service | — | dev.crm | qa.crm | crm.qrbring.com | crm / crm-api |
| pms-service | — | — | qa.pms | pms.qrbring.com | pms-api |
| ShortLinkAPI | — | — | — | short.qrbring.com | short-api |
| rnb-api-engage | 7666 | — | — | — | — |
| rnb-crons | 3200 | — | — | /var/www/rnb-cron and /var/www/rnb-crons | rnb-crons / cron-jobs / dev-cron |
| ingestion | — | dev.ingestion | — | /var/www/ingestion | ingestion |
| SimphonyIntegrationGen1 | — | — | qa.simphony | simphony.qrbring.com | simphony |
| wearOs API | — | — | — | /var/www/wearosapi | wearos-api |
| ringnbringncare | gRPC 50051/2 | dev.payment | — | /var/www/ringnbringncare | — |
| rnbresto-emitter | static | — | — | emitter.qrbring.com | — |
| rnbresto-adminDashboard | static | dev.portal | qa.portal | /var/www/portal | — |
| rnbresto-receiver-app | static | dev.receiver | qa.receiver | — | receiver |
| rnbresto-salesdashboard | static | dev.sales | qa.sales | /var/www/sales | — |
Hosts shown without .qrbring.com where the pattern holds. A dash means that environment has no
pipeline in bitbucket-pipelines.yml — not that the service doesn't exist there.
Amber marks a name that differs between environments.
Every deploy is a manual button press that copies files onto a live server
bitbucket-pipelines.yml is 44 KB of pipelines with no branch triggers at all — nothing deploys on merge.
Each service has Dev, QA and Prod variants that follow the same six steps.
PROD-deploy-micromenu by hand in Bitbucket.
Confirm variable is set to YES.
14.17.3 — the pipeline's base image.
/opt/prod-builds/<svc>/old, then emptied. One generation only.
/var/www/<host>, env pulled from /opt/envs/<svc>/<stage>.
firebase deploy.