The gap this closes: nix-cafe had no call to the loyalty backend’s /pos/*
routes, so a purchase never earned points — a member only gained points from the signup
bonus and spin rewards, and coupons could not be redeemed at the till. Narong asked for the scan to live in the
existing Select Customer dialog, and for the Customers page to list registered
app users with Points and Chances. Both are here.
get-coffee.staging.nixtech.app,
Starter on lumiere-coffee.staging.nixtech.app, against the staging loyalty backend.
Scan → attach → apply coupon ($2 → $1) → Pay → points
13 → 14, coupon used, order row loyalty_earn_status=earned.
Staging smoke 5/5 (no regression). nix-cafe 4f36c63, loyalty backend
ee50e93, nix-db migration 20260710120000, app 8f9ffc5.
Parked on staging for the scheduled prod promote.
Checks:
processing chance does not count)used, delisted) — never before, since redeem has no un-redeemcafe.orders row records member id, coupon id, points_earned=1, earn_status=earned





Earn and redeem run after the order transaction commits and are not awaited: the loyalty backend is
scale-to-zero on Fly, so a cold call must never delay a cashier or fail a paid sale. The order carries
loyalty_earn_status='pending' until the call reports back, so points lost to a timeout are recoverable
(a retry sweep is not yet built). Coupon redeem has no un-redeem, which is why it fires post-commit.
nix-cafe customer-picker.tsx, member-attach-row.tsx, qr-camera-scanner.tsx,
register-shell.tsx, api/cafe/orders/route.ts, api/loyalty/members/route.ts
(4f36c63) · nix-loyalty-backend routes/pos.ts + routes/admin.ts
(ee50e93) · nix-db 20260710120000_cafe_orders_loyalty_columns ·
nix-loyalty-app coupon QR (8f9ffc5) · harness test-pos-loyalty-staging.mjs.
Not covered: the camera scanner (no camera in headless) — the wedge/typed path is what the tests drive.