--- type: concept tags: [parking, domain, business, devices, entry-flow] sources: [] updated: 2026-06-15 status: open --- # Ticket Encoding & Scanning How a transient [[parking-session]]'s **ticket id** is printed, carried by the customer, and read back at the pay station and exit. This is the **physical backbone of the transient flow** — the thing that links entry → pay → exit when there's no plate. ## The ticket id is the session key At entry the system mints a `vehicle_entry` event with a **ticket id** (`identity`) and prints a ticket the customer keeps. That same id is read back later to find the session. Properties the id must have: - **Opaque + unguessable** — a random id (not a sequential count an attacker could iterate to claim someone else's cheaper session). Sequential **physical** stock numbering is a separate reconciliation aid ([[reconciliation]] pre-numbered stock), not the scan key. - **All-numeric** (as-built 2026-06-17) — so ANY legacy 1D barcode scanner reads it and an operator can hand-key it. Random (not sequential), so "all-numeric" does not weaken the unguessable property. Format: **13 digits = 12 cryptographically-random digits + 1 Luhn check digit** (10^12 space → negligible collisions at lot scale; the Luhn digit lets manual entry reject a typo rather than fail as "session not found"). `newTicketId()` in `apps/server/src/entry-flow.ts`; validate with `validateTicketCode()` (gate MANUAL entry only — a scanned/looked-up id already in the ledger is authoritative regardless of format). - **Format is a property of minting, not the schema** — `identity` / `sessions.id` are free-form `text`, so changing the id format is a code change with **no migration**. Legacy `T-` ids (pre-2026-06-17) remain valid keys and coexist with numeric ones. - **Single logical session** — scanning it at the pay station finds the open session; after payment it's the proof-of-paid the exit checks. ## Encoding: Code128 numeric barcode — printed by the booth dispenser - The [[rongta-printer]] prints the ticket id as a **1D Code128 barcode** (the all-numeric code), with the **same code in large human-readable digits below it**, then the entry time. Code128 over QR for the primary symbology because the booth's reader hardware is unknown and a legacy 1D laser scanner is the lowest common denominator — and the printed digits mean total reader failure still leaves a hand-keyable code. A **QR for phone/imager scanning may be added later behind an admin toggle** (deferred — see Open). > **As-built (2026-06-17).** `renderTicket()` in [[rongta-printer]] > (`packages/devices/src/drivers/printer-rongta.ts`) emits the Code128 via ESC/POS `GS k` (code set > B) — **rendered by the printer firmware**, so there is no image-rendering step and no new > dependency (keeps the MIT/Apache/BSD constraint). Resilience rationale: the booth's reader is > uncertain, so the id is carried in two independently-readable forms (1D barcode / printed digits). > The "operator scans with a phone" path reuses the > existing dispatch flow ([[entry-exit-readers]]) and is tracked separately (not yet built). - **Scan points** (both host-side reads — [[entry-exit-readers]]): - **Pay station** — customer scans the ticket → host finds the session → shows fee → takes payment ([[tariff]], pay-on-foot) → appends `payment`. - **Exit lane** — customer scans the (now paid) ticket → host validates paid + within `gracePeriodExit` → `vehicle_exit` → `pulseOpen`. - The **scanner is a device behind an adapter** ([[device-adapter-pattern]]): a new `ReaderDevice` kind (QR/barcode imager) — likely the same `IdentitySource = "ticket"` / `"qr"` path. Keeps the app device-agnostic; hardware model is procurement ([[bom]], [[open-questions]]). - **On hand:** the **[[gee-qr-er80]]** QR access reader (`-Q-W`: QR scanner, Wiegand/RS-232/RS-485, Linux-supported) — the concrete scanner for this path. A serial `ReaderDevice` adapter feeds the `read` bus; pending the reader's RS-232 frame/baud (see [[gee-qr-er80]] open questions). ## Ticketless alternative (plate as the ticket) Where the [[opencv-anpr-service|vision service]]/LPR captures the plate, the **plate can be the session key** instead of a printed ticket — drive in, plate read, drive to pay station and enter plate (or it's looked up), pay, exit by plate. No paper. The two can coexist per lane ([[entry-exit-readers]] "both share a relay"); a printed QR ticket is the fallback when a plate isn't captured or is low-confidence (recognition is advisory — [[opencv-anpr-service]]). ## Open - Primary symbology **decided**: Code128 set B over the all-numeric id (as-built above). Still open: what *else* prints (site name, tariff summary, help number). - **Optional QR (deferred)** — an admin toggle to ALSO print a QR for phone/imager users. The `code128()`/`qrCode()` ESC/POS helpers were prototyped 2026-06-16; QR was dropped 2026-06-17 in favor of "1D barcode + hand-keyable numeric code" because the booth's reader hardware is unknown. Revisit when mobile scanning is wanted. - **Phone-scan fallback** (operator scans a ticket with a phone when a reader is down) — designed but not built: an authenticated route feeding the same dispatcher + a minimal mobile scan UI. - Scanner hardware (imager model; same unit at pay station and exit?). - Lost/damaged ticket → the lost-ticket path ([[parking-session]], [[tariff]] admin-arbitrary amount).