# Integrations & devices ## Dispatch (order → store) Same idea as Ordit's `RestaurantSoftwareSetting` + `OrderingSoftwareSending`, without the shared-DB hack. ### DispatchChannel `id, location_id, type (stream | email | kitchen_display | printer | webhook), name, config (encrypted json), priority, role (primary | fallback | copy), enabled, send_offset_minutes, last_success_at, last_error_at`. Typical Eat Activ setup per store: 1. `stream` — primary, orders go to the POS/KDS through Stream. 2. `email` — fallback when Stream fails past SLA, + a daily "copy" summary. 3. `kitchen_display` — our own board (admin live view on a store tablet), always on. ### OrderDispatch `id, order_id, channel_id, status (queued | sending | sent | acknowledged | failed | cancelled), attempts, next_attempt_at, external_ref, request json, response json, last_error, created_at`. - Queue job per row, exponential backoff, alert after N failures. - Scheduled orders are dispatched at `promised_for − prep − send_offset`. - Driver interface (PHP): `send(OrderPayload): DispatchResult`, `cancel(Order)`, `supportsStatusUpdates()`. `OrderPayload` = neutral DTO (lines tree, net/gross/VAT, customer, slot) like Ordit's `Presentation\Ordering`. ## Stream Stream is the Deliverect equivalent: order-aggregation middleware that brings marketplace orders (Deliveroo, Uber Eats, Just Eat) into the POS. We push our click & collect / kiosk orders into it as another channel; delivery stays with the marketplaces. **No existing code in Ordit/Atis — built new.** **[Q: API docs + sandbox access via the Ordit contact]** Expected shape (to confirm against their API): - Outbound: create order, cancel order (our channel id = Stream "channel link" per location). - Inbound webhooks (HMAC-verified) → `IntegrationEvent` → handlers: - order status (accepted / preparing / ready / collected) → state machine transitions, - snooze / unsnooze items → `AvailabilityOverride(snooze_source = stream)`, - busy mode / store open-closed → `Location.paused_until`, - prep-time update → `LocationService.prep_time_minutes`, - menu push → catalogue sync (only if POS is menu master **[Q]**). - Mapping by `external_refs.stream` on Product / Modifier; unmapped items block menu publish with a clear error. ### Who owns the menu? | Option | Pros | Cons | |---|---|---| | A. POS (via Stream) is master, we import | one place for staff, POS prices = online prices | our rich content (photos, allergens, nutrition, dietary) needs a side layer; sync conflicts | | B. Our admin is master, Stream gets PLU mapping | full control, great admin UX | POS menu maintained separately | | C. Hybrid: POS owns item/price/PLU, we own content + merchandising | realistic | sync rules to define | **Decided: C (hybrid).** ### IntegrationEvent Raw log of every inbound/outbound integration call: `id, integration, direction, event_type, external_id, payload, status (received | processed | failed | ignored), error, processed_at`. Replayable from admin. ## Email & notifications - Provider: Postmark or SES (transactional) **[Q]**; templates in-repo (MJML → HTML), branded per design spec. - Customer: order confirmation, ready for collection, cancelled/refunded, receipt (kiosk on request), account (verify, reset, magic link). - Store: dispatch fallback email, daily summary, "Stream down" alert. - Push (app) via FCM/APNs: ready for collection, order status. ### NotificationLog `id, channel (email | push | sms), template, recipient, subject?, order_id?, customer_id?, status (queued | sent | delivered | bounced | failed), provider_message_id, error, sent_at`. ## Devices (kiosks, store tablets) ### Device `id, location_id, type (kiosk | kitchen_display | store_tablet), name, status (pending | active | disabled), hardware_id, app_version, last_seen_at, public_key?, settings json, payment_terminal_id?`. Kiosk `settings`: idle timeout (e.g. 45 s + 15 s countdown), attract media playlist, eat-in enabled, upsell rules, printer config, loyalty prompt on/off. ### Activation (improved Atis flow) 1. Admin creates device → one-time 6-digit code + QR, valid 15 min (`devices.manage`, audited). 2. Kiosk app scans / types code, sends hardware id + generated public key. 3. API returns a device token bound to location; later requests are signed (Ordit tablet idea). 4. Heartbeat every 60 s → `last_seen_at`; offline > 5 min → alert on admin board. 5. Remote actions: disable, reload, change settings — audited. ### Payment terminal Stripe Terminal (first) or Adyen terminal (cloud Terminal API), paired to the kiosk device. Kiosk never sees card data. Atis used Five (five.id) — same blocking/idempotent flow applies to any provider.