Skip to content

Slice C2: online ordering + simulated checkout - #17

Merged
DanMat merged 1 commit into
nimbus-rebuildfrom
slice/online-ordering
Sep 6, 2026
Merged

DanMat merged 1 commit into
nimbus-rebuildfrom
slice/online-ordering

Conversation

@DanMat

@DanMat DanMat commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The first public write surface in the app: a takeaway ordering flow that drops orders into the existing kitchen queue, with a simulated payment (no real money, no processor, no card capture).

What

  • Order model (folded into the unreleased 002_orders): table_id nullable, channel enum (dine_in/online), customer_name/customer_phone, confirm_token, and a tiny per-IP rate table. Dine-in unchanged.
  • Orders::placeOnline() — snapshots name + price from the published menu (client prices never trusted), computes the total server-side, caps qty + line count, requires name + phone, creates a paid sent table-less order in one transaction. confirmToken() / onlineForConfirmation() make the confirmation non-enumerable (token-gated, constant-time).
  • Public surface (plugin-owned auth, ADR 0017): a themed, JS-free page /order (ADR 0023) + POST /ext/restaurant/order, guarded by a honeypot + per-IP throttle (RateLimiter) + order-size caps.
  • Kitchen shows online tickets as “Online · name”; Reports counts online sales automatically. Nav gains Order; seed adds one sample online order.

Security

Full Attacker/Defender/QA review in docs/design/slice-c2-online-ordering.md. Key controls: server-snapshotted prices (no total tampering), escape-on-render for guest name, token-gated confirmation (no IDOR), throttle+honeypot+caps for the public write, and a payment that is a labelled simulation (payment_method='online-demo') that never touches money or card data.

Tests

OrdersTest (snapshot-not-posted-price, unknown-item-dropped, empty rejected, name/phone required, reaches kitchen, token-gated confirmation) + RateLimiterTest (limit/reset/per-IP). 89 plugin tests green, PHPStan L6 + cs-fixer clean.

🤖 Generated with Claude Code

Add a public takeaway flow: a themed order page (menu + qty + name/phone), a
public POST that places a table-less order straight into the kitchen queue, and a
token-gated confirmation. The checkout is a clearly-labelled DEMO — no real
payment, no processor, no card data.

- Order model (folded into the unreleased 002_orders): table_id nullable, a
  channel enum (dine_in/online), customer_name/phone, and a confirm_token; plus a
  tiny per-IP rate table. Dine-in unchanged.
- Orders::placeOnline() snapshots name+price from the menu (client prices never
  trusted), computes the total server-side, caps qty + line count, requires a
  name + phone, and creates a paid 'sent' online order in one transaction.
  onlineForConfirmation()/confirmToken() make the confirmation non-enumerable.
- Public surface (plugin-owned auth per ADR 0017): themed page /order (ADR 0023,
  JS-free) + POST /ext/restaurant/order, guarded by a honeypot + per-IP throttle
  (RateLimiter) + order-size caps. Kitchen shows online tickets as 'Online · name';
  Reports counts online sales automatically.
- Nav gains Order; seed adds one sample online order.

Design + 3-hat + full Attacker/Defender/QA security review in
docs/design/slice-c2-online-ordering.md. Tests: placeOnline (snapshot/caps/
table-less/kitchen/token) + RateLimiter; 89 plugin tests green, PHPStan L6 +
cs-fixer clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DanMat
DanMat merged commit a22b79a into nimbus-rebuild Sep 6, 2026
8 checks passed
@DanMat
DanMat deleted the slice/online-ordering branch September 6, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant