ByteBite Docs
Architecture

Accept, reject & timeout

Operator accept/reject and the 10-minute pending-order timeout.

Accept, reject & timeout

The fifth product slice is Accept / Reject + Annahme-Timeout. An operator accepts or rejects a pending pickup order. If the 10-minute deadline elapses with no action, a DB-backed scheduler cancels the order. Payment stays cash / unpaid. There is no payment provider.

Checkout still creates the pending order. See Checkout & orders.

Transitions (ADR-015)

Only these writes exist in this slice:

FromToHow
pendingacceptedDashboard accept + ETA
pendingcancelledDashboard reject (restaurant_rejected) or timeout (acceptance_timeout)

No cancel after accept. No customer cancel. Kitchen after accept is Slice 6: Kitchen status.

Expired pending orders cannot be accepted or rejected (409). The timeout tick wins.

Double-accept, accept after reject, and reject after accept are 409. A second timeout tick on the same order is a no-op.

Accept body

{ "etaMinutes": 15 }

Allowed values: 15 | 30 | 45 | 60. 60 is the PRD “60+”. Other values → 400. ETA is persisted only on accept. Query/body restaurantId is ignored; the dashboard path is the restaurant (ADR-021).

Reject has no required comment.

Columns added on orders

ColumnNotes
eta_minutesNullable int. Set only on accept. Whitelist 15/30/45/60
cancellation_reasonNullable enum of existing reasons. null until cancelled

acceptance_deadline_at remains created_at + 10 minutes (Slice 4). A partial index on pending + deadline backs the scheduler.

Scheduler (ADR-016)

DB-backed, inside the API process. No BullMQ/Redis, no new queue package.

Each tick is one idempotent UPDATE:

UPDATE orders
SET order_status = 'cancelled',
    cancellation_reason = 'acceptance_timeout'
WHERE order_status = 'pending'
  AND acceptance_deadline_at <= now();

Accept/reject writes also check isAcceptanceExpired and acceptance_deadline_at > now() so a late click cannot beat the timeout.

Successful accept, reject, and timeout each send one transactional SMS after the write (restaurant name + ETA on accept). 409 and foreign-tenant 404 do not notify. Checkout still does not send. See Transactional notifications.

Dashboard API

Requires a staff session. Restaurant context is the path, not the storefront hostname.

MethodPathBody
POST/dashboard/restaurants/:restaurantId/orders/:orderId/accept{ etaMinutes: 15 | 30 | 45 | 60 }
POST/dashboard/restaurants/:restaurantId/orders/:orderId/rejectoptional / ignored restaurantId
GET/dashboard/restaurants/:restaurantId/orders—
GET/dashboard/restaurants/:restaurantId/orders/:orderId—

GETs return etaMinutes and cancellationReason (null until set). List is newest first and shows the real status.

A Luigi orderId under Mario's restaurantId is 404 and is not written.

Storefront

GET /storefront/orders/:orderId returns the same fields. Confirmation shows pending, accepted (with ETA), or cancelled (rejected vs timeout). The page polls the existing GET. A Luigi orderId on Mario's host is 404 / OrderUnavailable.

Isolation

Seeds still create no orders. Isolation is proven by real checkouts, then accept/reject/timeout.

  • Luigi checkout → Luigi accept etaMinutes: 30 → accepted, ETA 30, snapshot prices unchanged.
  • Same Luigi orderId under Mario's dashboard path → 404, status stays pending.
  • Luigi reject → cancelled / restaurant_rejected.
  • Pending with a past deadline: accept/reject → 409; tick → acceptance_timeout; second tick no-op.
  • Body restaurantId does not change the tenant.

Not in this slice

BitePay/Stripe, card_at_door, capture/refund/void, delivery, address, minimum order, delivery fee, customer accounts, variants/extras/allergens, cancel after accept, customer cancel, checkout SMS, e-mail (no customer_email column), sound/PWA, production deploy. Closed checkout 409: Opening hours. Kitchen after accept: Kitchen status. Dashboard session: Staff authentication. Transactional SMS on accept/reject/timeout: Transactional notifications.

On this page