Skip to main content

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 typeDirectionPurpose
BillPreparationRequestedOpenApp -> POSAsk the POS to prepare the bill for the scanned station.
BillPreparationResultPOS -> OpenAppReturn the prepared bill and tip recipient identifier, or a rejection reason.
BillPaymentCompletedOpenApp -> POSTell the POS that the OpenApp payment succeeded.
BillPaymentFailedOpenApp -> POSTell the POS that the OpenApp payment failed; prepared state should be released.
BillPaymentResultPOS -> OpenAppConfirm 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, and stationKind (TABLE or KIOSK). No tip recipient.
  • Receipt QR - an OpenApp URL the POS builds at print time (see Building the receipt QR) embedding the stationId and the tipRecipientId of the person printing the receipt. On scan, OpenApp reads those values and resolves to a location, stationId, stationKind, and tipRecipientId.

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:

  1. Receipt QR - when the customer scanned a receipt QR, the embedded tipRecipientId is authoritative and is used regardless of what the POS returns later.
  2. BillPreparationResult.tipRecipientId - for the station-QR path (no embedded tip recipient), the POS returns the tip recipient for the station in BillPreparationResult. 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.