Technical / Part 6
Part 6 β QR & Cup Identity
The machine-readable half of the label: QR payloads, the compact URI scheme, cup IDs with check digits, the return lifecycle, and deposit accounting.
6.1QR payloads
Every L3 label β and every Part 1 Β§1.5 compact label β carries a QR. Three payload forms, in order of preference:
| Form | Payload | When |
|---|---|---|
| Short link | https://<host>/l/{label_id} | An L3 backend exists. Smallest module count β most scannable on curved, wet cups. |
| Deep link | Part 5 Β§5.1 fragment URL | No backend β the label is self-contained; the QR is the database. |
| Compact URI | dbnf1: scheme (Β§6.2) | Closed-loop scanners (return kiosks, dish lines) that never open a browser. |
6.1.1 Physical QR requirements
| Parameter | Requirement |
|---|---|
| Error correction | Level M minimum; Q SHOULD be used on cold cups (condensation abrasion) |
| Module size | β₯ 0.33 mm printed (203 dpi: β₯ 3 dots/module) |
| Quiet zone | 4 modules all sides; MUST NOT wrap > 15Β° of cup curvature (Part 1 Β§1.6.3) |
| Payload budget | β€ 2,048 bytes (fits QR version 25 @ EC M with margin) |
| Contrast | Dark-on-light only; inverted QRs fail too many scanner defaults |
6.2The dbnf1: URI scheme
dbnf-uri = "dbnf1:" payload
payload = "l/" label-id ; a Part-5 label reference
/ "c/" cup-id ; a bare cup identity (return scans)
/ "s/" b64-state ; an inline Part-5 Β§5.1.1 state blob
label-id = 1*32( ALPHA / DIGIT / "_" / "-" )
cup-id = <Β§6.3 grammar>
b64-state = 1*2048( ALPHA / DIGIT / "+" / "/" / "=" )
Examples: dbnf1:c/A92F-3 Β· dbnf1:l/lbl_9f2ke1. Return-line scanners MUST accept all three forms and MUST extract a cup ID from form l/ by resolving the label (or rejecting gracefully offline). Browsers won't resolve dbnf1: β it exists precisely for hardware that shouldn't need the web to accept a cup back.
6.3Cup ID grammar & check digit
cup-id = body [ "-" check ]
body = 4*8HEXDIG ; uppercase
check = HEXDIG ; Luhn mod-16 over body
Fleets over 50,000 cups SHOULD use the check digit β hand-keyed returns at busy counters mistype, and a wrong-cup refund is a ledger correction nobody enjoys. The algorithm is Luhn generalized to base 16:
function luhn16(body) { // body: uppercase hex string
let sum = 0, dbl = true; // double from the rightmost digit
for (let i = body.length - 1; i >= 0; i--) {
let d = parseInt(body[i], 16);
if (dbl) { d *= 2; if (d >= 16) d = d - 16 + 1; }
sum += d; dbl = !dbl;
}
return ((16 - (sum % 16)) % 16).toString(16).toUpperCase();
}
luhn16("A92F") // β "3" β full ID "A92F-3"
// verify: luhn16(body) === check β single-digit errors and adjacent
// transpositions are detected
The label strip renders the ID as CUP #A92F-3 in monospace (Part 1 Β§1.2 row 16). IDs are unique per fleet, not globally β the tuple (fleet, cup-id) is the global key, carried in API paths by the key's location scope (Part 5 Β§5.4).
6.4Lifecycle state machine
issue sale return-scan wash-log
ββββββββββ ββββββββββ ββββββββββ βββββββββββ βββββββββ
β issued β βββΆ β clean β βββΆ β in_use β ββββββΆ βreturned β βββΆ βwashingβ
ββββββββββ ββββββββββ ββββββββββ βββββββββββ βββββ¬ββββ
β² β TTL expiry β pass QA
β βΌ β cycles++
β ββββββββ βββββββββββ β
ββββββββββββ€ lost β β retired ββββββββββββ΄ fail QA /
found & ββββββββ βββββββββββ max cycles reached
rewash
| Transition | Trigger | Side effects |
|---|---|---|
| clean β in_use | sale scan | deposit charged; label printed; label.created |
| in_use β returned | return scan (Β§5.5) | refund instruction issued; cup.returned |
| in_use β lost | no return within fleet TTL (default 30 days) | deposit forfeits to the reuse fund; cup.lost |
| lost β washing | late return ("found") | no automatic refund; fleet policy decides |
| washing β clean | wash log (batch, temp β₯ 71 Β°C / 160 Β°F recorded) | cycles++ |
| washing β retired | QA fail, or cycles β₯ max_cycles (vendor-rated) | removed from fleet counts |
All other transitions are invalid and MUST be rejected with 409 invalid_transition β including double returns, the classic deposit-fraud vector.
6.5Deposit ledger
Deposits are liabilities, not revenue. A conforming ledger is double-entry per cup:
sale scan DR customer_payment 1.00 CR deposit_liability 1.00
return scan DR deposit_liability 1.00 CR refund_payable 1.00
TTL forfeit DR deposit_liability 1.00 CR reuse_fund 1.00
Requirements: refunds MUST be honored across all locations sharing a fleet; the ledger MUST reconcile to count(in_use) Γ deposit at all times; forfeited deposits SHOULD fund cup replacement rather than margin (this is what makes the program defensible to regulators and customers alike).
6.6Privacy considerations
- QR payloads MUST NOT contain PII β no customer name, phone, or order ID that joins to one. A cup identifies a vessel, not a person.
- Return scans MUST NOT require an account; anonymous cash-out is the baseline. Loyalty linkage is opt-in at scan time.
- Label documents are recipe data and MAY be public (that's the point); scan event streams are behavioral data and MUST NOT be published at individual-cup granularity β publish aggregates (return rate, cycle counts).
- Fleet TTL processing means retaining scan timestamps; retain the minimum, and never join them to payment instruments beyond the refund transaction.