Scan & Pay
Scan & Pay is a stateless QR-initiated bill payment. A customer scans a QR code, OpenApp resolves it to a location and station, and immediately requests the current payable bill from the POS. OpenApp holds no station state between scans; every scan starts fresh.
The flow can start from either of two QR codes:
- A static station QR fixed to the station (table or kiosk). It carries no embedded identifiers.
- A receipt QR the POS prints on the receipt with the tip recipient embedded. When the customer pays, OpenApp attributes the tip to the recipient from the receipt.
This diagram is logical. It omits delivery-mode details; see Architecture for the delivery model.
Message types:
| Message type | Direction | Purpose |
|---|---|---|
| BillPreparationRequested | OpenApp -> POS | Ask the POS to prepare the bill for the scanned station. |
| BillPreparationResult | POS -> OpenApp | Return the prepared bill and tip recipient identifier, or a rejection reason. |
| BillPaymentCompleted | OpenApp -> POS | Tell the POS that the OpenApp payment succeeded. |
| BillPaymentFailed | OpenApp -> POS | Tell the POS that the OpenApp payment failed; prepared state should be released. |
| BillPaymentResult | POS -> OpenApp | Confirm or reject applying the OpenApp payment in the POS. |
How the QR resolves to a station
OpenApp resolves either code to a location and station, then builds the orderContext sent to the POS.
- Station QR - static, pre-provisioned once per station (table or kiosk). It is opaque and carries no internal identifiers; OpenApp resolves it server-side to a location,
stationId, andstationKind(TABLEorKIOSK). No tip recipient. - Receipt QR - an OpenApp URL the POS builds at print time (see Building the receipt QR) embedding the
stationIdand thetipRecipientIdof the person printing the receipt. On scan, OpenApp reads those values and resolves to a location,stationId,stationKind, andtipRecipientId.
Either way, the resolved context has the same shape:
{
"type": "STATION",
"stationId": "<resolved from QR>",
"stationKind": "TABLE" | "KIOSK"
}
Building the receipt QR
Receipt QR codes are dynamic, so the POS builds them itself at print time - there is no call to OpenApp. The POS composes an OpenApp URL with the stationId and the tipRecipientId of the person printing the receipt.
The QR value the POS is expected to produce:
https://o-app.co/R1/{stationId}_{tipRecipientId}
Minimal bill support
POS systems track itemized bill lines for OpenApp. The POS is only required to return the payable total; position identifiers, quantities, and prices are optional. A total-only response is valid.
Bill preparation rejection vocabulary: see Bill preparation rejection reasons.
Tip recipient
OpenApp resolves the tip recipient from one of two sources, in this order:
- Receipt QR - when the customer scanned a receipt QR, the embedded
tipRecipientIdis authoritative and is used regardless of what the POS returns later. BillPreparationResult.tipRecipientId- for the station-QR path (no embedded tip recipient), the POS returns the tip recipient for the station inBillPreparationResult. Return it whenever the POS can identify the recipient.
When neither source is present, tips default to merchant settlement. In both cases OpenApp uses the tipRecipientId only to attribute tips.