Draft — for team verification Ring n Bring · 35 deployables

How an order, a payment and a POS check actually move

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.

The short version

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.

Start here

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.

Guest a person at the table The app rnbresto-emitter The ordering API MicroMenuAPI · :7070 The shared database Firestore Background jobs Cloud Functions Outside world till · payment · WhatsApp 1 Scans the QR code printed on the table one QR per table 2 Opens the venue's menu, guest builds a cart Angular + Ionic 3 Checks it: real table? Tab already open? Ordering too fast? POST /sendOrder 4 ★ The order is saved as ONE document — it now exists Orders/{orderId} 5 The save wakes them by itself — nobody calls them newOrderAdded 6 A check opens on the kitchen till, so the food gets made Simphony · Infrasys 7 Guest gets a WhatsApp; staff tablets and watches get a push Twilio 8 Guest pays on the provider's page; it confirms back Stripe · PayTabs · +7 9 ★ The payment lands on that same order — another write payments[] 10 Woken again: receipt the guest, tell the till it is paid newOrderUpdated

What actually happens at each step

1

The guest scans the QR code on the table

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.

2

The app loads that venue's menu and the guest builds a cart

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.

rnbresto-emitter · Angular 10 + Ionic 5

3

The ordering API checks the order before anything is saved

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.

MicroMenuAPI · POST /api/v2/sendOrder · staff apps use LogisticsAPI :7079 instead

4

★ The order is saved as one document — this is the moment it exists

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.

Firestore · resturant/{venueId}/Orders/{orderId} — the misspelling is real and load-bearing

5

Background jobs wake up on their own

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.

Cloud Functions · newOrderAdded (621 lines)

6

A check is opened on the kitchen till

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.

Oracle Simphony Gen1 & Gen2 · Infrasys · Foodics · Omega · TOTPOS

7

The guest and the staff are told

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.

Twilio for WhatsApp · Microsoft Graph for email · device push for staff

8

The guest pays

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.

Stripe · PayTabs · CCAvenue · Urway · JCC · PayOne · MontyPay · Whish · Network International

9

★ The payment result is written onto that same order

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.

payments[] on the order document

10

The background jobs wake a second time

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.

Cloud Functions · newOrderUpdated (1208 lines)

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.

Walk through it

Press Play to watch the whole journey build itself, or pick one flow to take on its own.

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

Order status

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.

defaultpay before the order is placed
starts at pending_payment
  • pending_paymentreceived pending canceled
  • receivedconfirmed canceled
  • pendingconfirmed canceled
  • confirmeddone
  • canceledpending_payment received pending
post-confirmstaff confirm first, guest pays after
starts at unpaid
  • unpaidpending_payment canceled
  • pending_paymentconfirmed canceled
  • confirmeddone
  • canceledpending_payment unpaid
normal / open-tabrun a tab, settle at the end
starts at received
  • receivedconfirmed canceled
  • pendingconfirmed canceled
  • failedconfirmed canceled
  • confirmeddone
  • canceledpending failed received
Cancelling is reversible. Every strategy allows canceled 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.
These rules guard exactly one route. 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.

Words you'll see

The vocabulary that carries most of the confusion

FirestoreGoogle's cloud database. Stores "documents" — think one JSON object per order.
Cloud FunctionA small program that runs automatically when a document changes. Nobody calls it; the save triggers it.
POSPoint of sale — the till the venue already runs. We open and pay "checks" on it.
PSPPayment provider (Stripe, PayTabs, JCC…). Takes the card details so we never do.
KafkaA queue. One service drops a message in, another picks it up later. Used only for payment results.
pm2The thing that keeps the Node services running on the servers, and restarts them on deploy.
checkThe POS's word for a bill. We "open a check" when an order arrives and "pay the check" when it's settled.
tab / sessionorderSession — several orders on one table, settled together. Closing it marks every confirmed order in it done.
emitter / receiverEmitter = the guest's app. Receiver = the staff tablet. Named for who emits and who receives the order.
ticketThe kitchen-facing copy of an order, created by newOrderAdded as a sub-collection.
strategyonlinePaymentStrategy — whether the guest pays first, after staff confirm, or at the end. It picks the status lifecycle above.
sub-orderA split of one order, used when a table pays separately. order-service keeps them in sync when a payment lands.
idempotencyDoing the same thing twice safely. Providers retry webhooks, so "this payment succeeded" can arrive several times.
resturantNot a typo you should fix — it's the actual Firestore collection name, and every service depends on the spelling.

Where everything runs

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.

ServicePortDev hostQA hostProductionpm2 name
MicroMenuAPI7070dev.mmqa.mmapiprod.qrbring.commicromenu
LogisticsAPI7079dev.logisticsqa.logisticslogisticsapiprod.qrbring.comlogistics
SocketAPI5545dev.socketqa.socketsocketsapiprod.qrbring.comsockets
integrationapi7678dev.integrationqa.integrationintegrationapi.qrbring.comintegration-api
PayTabIntegration7700
urwayintegration7699qa.urwayurway.integration.qrbring.comurway / urway-api
ccavuenepaymentapidev.ccavenueqa.ccavenue/var/www/ccavenueccavenue
stripe-integration7555/var/www/stripe-integrationstripe / stripe-integration
rnb-notificationsdev.notificationsqa.notificationsnotifications.qrbring.comrnb-notifications
crm-servicedev.crmqa.crmcrm.qrbring.comcrm / crm-api
pms-serviceqa.pmspms.qrbring.compms-api
ShortLinkAPIshort.qrbring.comshort-api
rnb-api-engage7666
rnb-crons3200/var/www/rnb-cron and /var/www/rnb-cronsrnb-crons / cron-jobs / dev-cron
ingestiondev.ingestion/var/www/ingestioningestion
SimphonyIntegrationGen1qa.simphonysimphony.qrbring.comsimphony
wearOs API/var/www/wearosapiwearos-api
ringnbringncaregRPC 50051/2dev.payment/var/www/ringnbringncare
rnbresto-emitterstaticemitter.qrbring.com
rnbresto-adminDashboardstaticdev.portalqa.portal/var/www/portal
rnbresto-receiver-appstaticdev.receiverqa.receiverreceiver
rnbresto-salesdashboardstaticdev.salesqa.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.

How it ships

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.

STEP 01Pick a pipeline Someone runs e.g. PROD-deploy-micromenu by hand in Bitbucket.
STEP 02Confirm = YES Prod steps exit unless the Confirm variable is set to YES.
STEP 03nvm use + npm install Node is switched per service, then reset to 14.17.3 — the pipeline's base image.
STEP 04Back up, then rm -rf Live folder is copied to /opt/prod-builds/<svc>/old, then emptied. One generation only.
STEP 05Copy code + .env Source copied to /var/www/<host>, env pulled from /opt/envs/<svc>/<stage>.
STEP 06pm2 restart Process restarted by name. Cloud Functions instead go out via firebase deploy.