Skip to content

Backoffice → Online Ordering Relations

How back-office settings flow to the Online Ordering storefront.

Scope & verification: This doc maps which back-office settings drive Online Ordering, verified against the backend (upvendo-backend) and back office (upvendo-backoffice). The customer-facing storefront app is a separate codebase and is out of scope; anything describing how the storefront renders a setting is tagged (storefront — not verified here). Field names, defaults, and enums below are taken from Constants::$DEFAULT_ONLINE_ORDERING_SETTING, Constants::getDefaultOnlineSettings(), StoreOnlineOrderingRequest, and StoreOnlineSettingsRequest.

Overview

Online Ordering for a location is driven by two separate settings objects on the Location record, plus the location's own profile, business hours, menus, and assigned payment profile:

  • online_ordering_setting — the channel configuration: pickup/delivery toggles, delays, cut-offs, scheduling, delivery region/fee, minimum order amounts, pickup instructions/fee, customer-info requirements, payment methods, idle timeout, send-to-POS, snooze. Edited at Online → Online Ordering (/online/online-ordering), validated by StoreOnlineOrderingRequest.
  • online_settings — operational settings: order capacity, delivery/pickup available days, tip collection, checkout notes, and menu assignment. Edited at Online Settings (/online-settings), validated by StoreOnlineSettingsRequest.

There is no enabled toggle and no accepting_orders field for online ordering. A location turns the channel on by subscribing to the Online Ordering channel (a Stripe subscription created on save), and pauses orders with Snooze (snoozed_until + the SnoozeOptions enum). See the Online Ordering feature doc.

┌─────────────────────────────────────────────────────────────────────────┐
│                           BACKOFFICE                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐         │
│  │  Locations  │ │    Menus    │ │   Online    │ │  Payments   │         │
│  │ + Bus.Hours │ │ (visibility)│ │  Settings   │ │  (Stripe)   │         │
│  └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘         │
│         │               │      ┌────────┴────────┐      │                │
│         │               │      │ Online Ordering │      │                │
│         │               │      │     setting     │      │                │
│         │               │      └────────┬────────┘      │                │
└─────────┼───────────────┼───────────────┼───────────────┼────────────────┘
          │               │               │               │
          ▼               ▼               ▼               ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                  ONLINE ORDERING STOREFRONT (out of scope)                │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐         │
│  │  Location   │ │    Menu     │ │  Time Slot  │ │  Checkout   │         │
│  │   Header    │ │   Display   │ │  Selection  │ │   Payment   │         │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘         │
└─────────────────────────────────────────────────────────────────────────┘

Settings Impact Matrix

Storefront-rendering effects in the "Online Ordering Impact" columns are the intended effect of each setting; the storefront app is not verified here.

Location Profile

The location record holds profile fields and the base preparation time. Online ordering is configured per location.

Backoffice SettingFieldOnline Ordering Impact
Location Namename (Location)Identifies the location in the back office and storefront
Addresslocation address fieldsUsed for delivery distance (Radius/Distance region modes)
TimezonegetTimezone() / TimezoneServiceOpen/closed checks and slot times resolved in this timezone
Average Preparation Timeaverage_prep_time (Location; default 20)Base prep time added to the earliest available slot for both channels
Payment Profilelocation's assigned payment profileWhich Stripe connected account processes online payments

Notes:

  • average_prep_time is a location-level field (getAveragePrepTime() returns intval), not part of online_ordering_setting. It is shown read-only on the Online Ordering page and set under Settings → Locations.
  • There is no per-location "enabled" or "currency" online-ordering toggle. Subscription state (channel subscribed) controls availability; currency is a location/tenant property, not an online-ordering field.

Business Hours

Business hours (business_hours, labelled "Operating hours") live on the location, keyed by capitalized day names MondaySunday, each with is_available and a times array of { from, to } (H:i).

Backoffice SettingFieldOnline Ordering Impact
Day availablebusiness_hours.{Day}.is_availableDay is open/closed for ordering
Time rangesbusiness_hours.{Day}.times[]Times within which the location is open
Online-ordering custom availabilitycustom_availability + schedule (online_ordering_setting)When on, online ordering uses this schedule instead of business hours
Restricted datesrestricted_dates (online_ordering_setting) / location restricted datesSpecific dates closed regardless of the weekly schedule
Temporary closuretemporarily_closed_until (online_ordering_setting)Location closed until a reopening date; future orders still allowed for dates on/after it
Show location hours onlyshow_location_hours_only (online_ordering_setting; default false)Display-only. The channel payload always carries hours.location; the separate hours.online_ordering schedule is sent only when custom_availability is on and this flag is off, otherwise null. Does not change ordering availability (RawModels/Location.php lines 1518, 1616)

Note: show_location_hours_only lives in online_ordering_setting but is read for both channels when the ordering payload is built, so it also suppresses the hours.qr_ordering key for table QR (RawModels/Location.php line 1616 — $hoursKey, set at :1508, is online_ordering or qr_ordering, both gated by the same flag).

Customer Experience (storefront — not verified here):

  • The open/closed check (checkForBusinessHours) resolves the requested time in the location's timezone, picks the online-ordering schedule (custom availability when enabled, else business hours), and rejects restricted dates, the temporarily-closed-until date, and same-/next-day cut-offs.
  • Time comparison is time >= from && time <= to on the H:i strings (no past-midnight wrap handling).

Online Ordering Setting (channel configuration)

All fields below live in online_ordering_setting (validated by StoreOnlineOrderingRequest, defaults from Constants::$DEFAULT_ONLINE_ORDERING_SETTING).

Backoffice SettingField IDDefaultOnline Ordering Impact
Enable Pickuptakeout.enabledtruePickup option shown/hidden
Pickup Delaytakeout.delay0Added to earliest pickup time
Enable Deliverydelivery.enabledfalseDelivery option shown/hidden (needs a delivery region)
Delivery Delaydelivery.delay0Added to earliest delivery time
Same-/Next-day cut-offstakeout/delivery.same_day_cutoff/next_day_cutoffenabled:false, time:nullClose same-/next-day ordering after the set time
Minimum Order Amountminimum_order_amount {enabled, amount}false / 10Orders below amount blocked at checkout when enabled
Pickup Minimum Order Amountpickup_minimum_order_amount {enabled, amount}false / 10Separate pickup-only minimum
Pickup Transaction Feepickup_transaction_fee {enabled, fee}false / 0Extra fee on pickup orders
Pickup Instructionspick_up_instructions {enabled, placeholder}false / "Add some instructions..."Free-text pickup guidance at checkout
Send to POSsend_to_pos (SendToPosOptions)pickup_delivery_timeOrder sent to POS/KDS at the scheduled time vs immediately when placed
Order Number Displayorder_number_display (OrderNumberDisplayOptions)BothOrder number, queue number, or both
Idle Timeoutidle_timeout_type / idle_timeout_seconds / show_warning_for_secondsStandard / 120 / 15Session idle handling
Menu Layoutmenu_item_columns (in:2,3,4)2Number of columns in the storefront menu-item grid (storefront rendering — see note)

There is no enabled, accepting_orders, or preparation_time field in this object. Prep time is the location's average_prep_time; pausing is via Snooze.

menu_item_columns is not the kiosk field of the same name. Online ordering validates nullable|integer|in:2,3,4 (StoreOnlineOrderingRequest.php line 222, default 2 at Constants.php:739) and the back office offers 2/3/4 Columns (src/views/online/online-ordering/forms/OnlineOrdering.vue lines 155-166, options at 776-780). The kiosk device profile has a separate menu_item_columns still restricted to in:2,3 (StoreDeviceProfileRequest.php line 211) — see Backoffice → Kiosk. The two settings are independent; do not reconcile them.

Storefront grid rendering, verified for this entry only: the storefront falls back to 2 columns for any other value, keeps a single column on the narrowest screens, and maps 4 to a responsive ladder (2 across at small → 3 at medium → 4 at extra-large) rather than a hard 4-up (zestidoo-online-ordering src/stores/app.ts lines 259-262; src/components/menu/MenuSection.vue lines 11, 57-65). All other storefront-rendering claims in this doc remain (storefront — not verified here).

Scheduling (online_ordering_setting)

Backoffice SettingField IDDefaultOnline Ordering Impact
Scheduled Pickupschedule_order_for_future_days.takeout.enabledtrueAllow scheduling pickup for future days
Pickup Days Aheadschedule_order_for_future_days.takeout.days_in_advance30 (max 365)How far ahead pickup can be scheduled
Scheduled Deliveryschedule_order_for_future_days.delivery.enabledfalseAllow scheduling delivery for future days
Delivery Days Aheadschedule_order_for_future_days.delivery.days_in_advance30 (max 365)How far ahead delivery can be scheduled
Scheduled Eat Inschedule_order_for_future_days.eat_in.enabledfalseAllow scheduling eat-in for future days
Eat In Days Aheadschedule_order_for_future_days.eat_in.days_in_advance30 (max 365)How far ahead eat-in can be scheduled
Enable Eat Ineat_in.enabledfalseShows the "Eat in" (For Here) option at checkout
Eat In Delayeat_in.delay0Extra minutes on top of prep time for eat-in
Last pickup slot at closing timetakeout.last_slot_at_closing_timetrueLast pickup slot is the closing time (on) or closing − prep (off); pickup only
Blocked timesblocked_periods[] {from, to, active_days}[]Recurring weekly windows that hide pickup/delivery slots
Custom Scheduled Orderscustom_scheduled_ordersfalseExposes hours_in_advance + minutes_before_pickup_delivery_time thresholds
Hours in Advancehours_in_advance(none; only present when custom_scheduled_orders is on)Threshold for delayed kitchen send
Minutes Before Pickup/Deliveryminutes_before_pickup_delivery_time(none; only present when custom_scheduled_orders is on)When to send a far-ahead order to the kitchen

Note: hours_in_advance and minutes_before_pickup_delivery_time are not seeded in the default array — they are conditional request fields added to validation only when custom_scheduled_orders is true.

Note on accept_orders_until: it is not a back-office control for online ordering. There is no UI for it on the Online Ordering page — the only occurrence in the backoffice for this channel is a form default (upvendo-backoffice src/store/modules/onlineOrdering.ts line 74). It remains in the save payload and in the backend default (Closing Time, app/Constants.php line 590), is still validated (StoreOnlineOrderingRequest.php line 169) and is still read when computing slot cut-offs (app/Services/OrderCapacity/OrderCapacityService.php lines 2133, 2292) — merchants simply cannot edit it. The editable closing-edge control is takeout.last_slot_at_closing_time; see Scheduled Orders. The editable accept_orders_until selector belongs to table-QR ordering only (StoreQrOrderingRequest.php line 27, default Closing Time Minus Prep Time at Constants.php line 306).

Back-office UI for the rows above: src/views/online/online-ordering/forms/FullfilmentAndScheduling.vue (last pickup slot line 130; blocked times lines 151-165 and 512-518; scheduled eat-in lines 304-329) and src/views/online/online-ordering/forms/OnlineOrdering.vue (eat-in enable/delay lines 375-394).

Order Capacity (online_settings)

Order capacity lives in online_settings.order_capacity (validated by StoreOnlineSettingsRequest, defaults from $DEFAULT_ORDER_CAPACITY_SETTINGS). Enforcement at order time is handled by OrderCapacityService (runtime enforcement not detailed here).

Backoffice SettingField IDDefaultOnline Ordering Impact
Capacity master toggleorder_capacity.enabledfalseWhen off, slots fall back to 30-min slots, all available
Time Slot Durationorder_capacity.time_slot_duration_min15Slot interval for time picker
Limit Orders per Slotorder_capacity.limit_orders_per_time_slot + number_of_orders_per_time_slotfalse / nullCaps orders per slot; full slots filtered out
Limit Items per Slotorder_capacity.limit_items_per_time_slot + number_of_items_per_time_slotfalse / nullCaps items per slot
Flexible Capacityenable_flexible_limit_order_per_time_slot + value_threshold_per_time_slot + allow_up_tofalseAllow extra orders above the limit when value threshold is met
Category Limitsenable_limit_specific_categories + limit_specific_categories[]false / []Per-category item caps per slot
Time-Specific Rulesorder_capacity.time_specific_rules[][]Named overrides (time range + active days) of the global limits

Customer Experience (storefront — not verified here):

  • Slots are generated per day from the online-ordering schedule, then (when capacity is on) batch-checked against limits; past and full slots are filtered out, and the result is cached ~60s per location/channel/day.
  • Booking a slot uses a cache lock with retry to avoid double-booking.

Delivery Region (online_ordering_setting)

Stored as a single delivery_region object (not multiple named zones). See Delivery Zones.

Backoffice SettingField IDDefaultOnline Ordering Impact
Region Typedelivery_region.type (DeliveryRegionOptions)RadiusSelects Postal Code / Radius / Distance eligibility method
Postal Codesdelivery_region.postal_codes[]Eligible postal codes (Postal Code mode)
Radiusdelivery_region.radius10Straight-line km (Radius mode)
Max Distancedelivery_region.max_distance10Driving-route km (Distance mode)

Delivery Fee & Minimums (online_ordering_setting)

Stored as a single delivery_fee object. See Delivery Settings.

Backoffice SettingField IDDefaultOnline Ordering Impact
Fee Typedelivery_fee.type (DeliveryFeeTypes)Free deliveryFree / charge-all / free-over-threshold
Base Feedelivery_fee.fee0Fee charged at checkout
Free-delivery Thresholddelivery_fee.minimum_order_amount_for_free_delivery0Order amount that qualifies for free delivery
Custom Fees by Postal Codedelivery_fee.custom_fees[][]Per-postal-code fee overrides (Postal Code mode)
Distance Pricingdelivery_fee.distance_pricing_enabled + distance_pricing_type + distance_fees/flat_rate_per_kmfalse / tieredTiered or flat-rate-per-km pricing (Radius/Distance mode)
Delivery Minimumminimum_order_amount {enabled, amount}false / 10Delivery minimum (this is the shared minimum_order_amount)

Customer Experience (storefront — not verified here): address in region → fee resolved by mode (custom postal-code fee or distance pricing); address outside region → delivery unavailable.

A menu appears in Online Ordering when its visibility array includes the Online Ordering channel (ChannelOptions::OnlineOrdering = 'Online Ordering'). Which menu(s) the storefront shows is set in Online Settings (default_menu_id + extra_menu_ids).

Backoffice SettingField IDOnline Ordering Impact
Menu visibility includes "Online Ordering"visibility (Menu)Menu eligible to appear on the channel
Menu status Publishedstatus (Menu)Draft/Archive menus excluded
Menu availabilityavailability_type (always-available / location-default / specific-day-time)When the menu is offered
Default menuonline_settings.default_menu_idPrimary menu shown
Extra menusonline_settings.extra_menu_idsAdditional menus, shown per their own schedules (no schedule overlap allowed)

Items/prices/modifiers live on display groups and items within the menu, not on the menu itself. (Modifier UI enforcement — required/min/max — is storefront behavior, not verified here.)

Item Stock (online_ordering_setting)

Two toggles on the Online Ordering page's Fulfillment and Scheduling tab. Both are rendered only when the location has at least one inventory-tracked item (v-if="hasInventoryTracked", fed by GET /back-office/inventories/has-trackedsrc/views/online/online-ordering/forms/FullfilmentAndScheduling.vue lines 343, 449-460; routes/api/backoffice/inventories.php line 9). See Stock Management and Inventory.

Backoffice SettingField IDDefaultOnline Ordering Impact
Continue selling items when out of stocksell_without_stockfalseItems stay orderable at zero/negative tracked stock; passed to InventoryService::manageLocationStocks(sellWithoutStock: …) when an online order is placed (OnlineOrderingOrchestrator.php lines 462-463)
Show remaining item stock to customersshow_item_stockfalseDisplay-only — reveals the remaining count on inventory-tracked items; does not change availability or stock enforcement

Notes:

  • sell_without_stock is resolved per channel at payment capture: kiosk transactions read the device profile's own sell_without_stock, online-ordering transactions read this location setting (PaymentCaptureService::resolveSellWithoutStock, PaymentCaptureService.php:662).
  • The two interact: with sell_without_stock on, available stock is treated as unlimited, so no count is displayed even when show_item_stock is on (verified for this entry: zestidoo-online-ordering src/stores/app.ts lines 266, 292-297; src/components/FoodCard.vue lines 176-182). Items are also still blocked by their own Inactive / Unavailable / Hidden status regardless of sell_without_stock (src/stores/app.ts lines 339-344).

Tips & Notes (online_settings)

These live in online_settings, not online_ordering_setting.

Backoffice SettingField IDDefaultOnline Ordering Impact
Collect Tipscollect_tips.enabledfalseTip selection shown at checkout
Tip Optionscollect_tips.options (3 values, 1–100)[10, 15, 20]Suggested tip percentages
Calculate Tipscollect_tips.calculate_tips (CalculateTipsOptions)"After taxes"Before/after-taxes basis
Checkout Notesallow_notes {enabled, placeholder}false"Add a note" field at checkout
Delivery/Pickup Daysdelivery_available_days / pickup_available_daysall 7 daysDays each channel is offered

Customer Info (online_ordering_setting)

Customer-info requirements are two enum fields, not independent require-name/phone/email booleans.

Backoffice SettingField IDDefaultOnline Ordering Impact
Full name requirementcustomer_information_full_name (CustomerInformationFullNameOptions)first_last_nameRequire first+last (first_last_name) or first only (first_name)
Contact requirementcustomer_information_contact_details (CustomerInformationContactDetailsOptions)phone_emailRequire phone+email (phone_email) or email only (email)

Customer Experience (storefront — not verified here): the chosen options determine which name/contact fields are required at checkout.

Payment Methods (online_ordering_setting + Payment Profile)

Online-ordering payments run through Stripe (the location's assigned payment profile's Stripe connected account) when Square is not connected; the channel→provider match maps Online Ordering → stripe (config('upvendo.zestidoo_payment_provider'), default stripe) and Kiosk → viva. See Payments.

Backoffice SettingField IDDefaultOnline Ordering Impact
Card / local methodsStripe connected-account capabilitiesCard, iDEAL, Bancontact, Klarna, etc. shown per the account's enabled capabilities (not per-method back-office toggles)
Invoice (Pay by Invoice)payment_methods.invoice_enabled (+ mplus_invoice_payment_code)falseInvoice option at checkout, Mplus tenants only; billed later via Invoice Collection
Cash on Deliverypayment_methods.cod_enabled (+ pos_cod_payment_code)falseCOD field exists but is not currently offered in production

Notes:

  • There are no per-method "iDEAL Enabled" / "Bancontact Enabled" back-office toggles — local methods follow the Stripe connected account's capabilities for the country.
  • When Square POS is connected (Merchant::isSquareIntegrated()), Square handles payments at the merchant-integration level; Stripe/Viva are bypassed. Square is not a branch in the per-channel provider match.
  • Free orders (total = 0) skip the payment provider entirely.

Snooze (pause without unsubscribing)

There is no "Accepting Orders" toggle. Pausing uses Snooze, written to online_ordering_setting.snoozed_until.

Snooze ActionSnoozeOptions valueEffect
Accept online ordersacceptResume; clears the snooze
Snooze 20 / 40 / 60 min20 / 40 / 60Pause until a timestamp; snoozed_until = Unix timestamp
Rest of the dayrest_of_the_dayPause until the start of the next day
Do not acceptdont_acceptIndefinite pause; snoozed_until stores the literal string dont_accept

snoozed_until is a runtime-written key on online_ordering_setting (not in the default array), holding either a Unix timestamp, the string dont_accept, or null. Snooze uses the location's timezone.


Detailed Field Mappings

Location Profile → Online Ordering

Location: name
    → storefront location identity / header (storefront — not verified here)

Location: average_prep_time (default 20)
    → base preparation time for the earliest available slot

Location: assigned payment profile (Stripe connected account)
    → which account processes online-ordering payments

Branding (logo, cover image, colors) is configured under Branding profiles, not the Online Ordering page; how it renders on the storefront is not verified here.

Preparation Time → Earliest Available Slot

average_prep_time (Location, default 20 min)
    +
takeout.delay  (pickup)   OR   delivery.delay  (delivery)
    =
Earliest available time = Now + average_prep_time + channel delay

Example (current time 12:00, average_prep_time 20, delivery.delay 15):
- Earliest pickup   = 12:00 + 20 + 0  = 12:20
- Earliest delivery = 12:00 + 20 + 15 = 12:35

The slot-check prep time is max(average_prep_time, largest item prep time) + channel delay, clamped to 0-1440 minutes — the largest item prep time is compared against the location average, not added to it, so it can only raise a quote above the location baseline, never lower it. The channel delay may be negative, which shortens the lead time; it is added as-is and only the final result is clamped. The item term is 0 today: it is gated by ONLINE_ORDERING_INCLUDE_ITEM_PREP_TIME, which defaults to false and is not set in production, staging or testing, so the calculation reduces to average_prep_time + channel delay. The nearby-restaurants list (prep_time.from / prep_time.to on GET /online-ordering/restaurant-suggestions/nearby) runs the same rule, with two differences worth knowing. It passes no cart, so $maxItemPrep takes its default 0 (:2074-2077) — browse and checkout therefore agree only while the item-prep gate stays off; switching ONLINE_ORDERING_INCLUDE_ITEM_PREP_TIME on would make a cart-laden checkout quote exceed the browse quote by design. And it maps only over getOnlineOrderingDiningOptions() (:1956-1968), which is Pickup and Delivery — never ForHere — so an eat-in-only location advertises nothing while browsing. (Verified: app/RawModels/Location.php:2001-2018getDiningOptionPrepTime(), the single definition; getOnlineOrderingPrepTime() at :2072-2087 and OrderCapacityService::effectivePrepMinutes at app/Services/OrderCapacity/OrderCapacityService.php:86-89 both call it; maxItemPrepFromSnapshot at :101-115; config/upvendo.php line 26.)

A location with neither takeout nor delivery enabled still appears in that list with prep_time {from: 0, to: 0} — that is "nothing to promise", not "ready immediately". The sibling dining_options is empty in exactly the same case and tells the two apart. Note the shared trigger: both are driven by takeout.enabled and delivery.enabled only, so a location offering online eat-in and nothing else has eat_in.enabled = true yet still reports an empty dining_options and a zero prep_time. Do not read an empty dining_options as "this location takes no online orders". (Verified: app/RawModels/Location.php:1956-1968 and :2079-2081.)

Menu.visibility includes "Online Ordering"  (ChannelOptions::OnlineOrdering)
    AND Menu.status = Published
    AND menu assigned in Online Settings (default_menu_id / extra_menu_ids)
    → menu appears on the Online Ordering channel (storefront — not verified here)

Real-Time Updates

Saving the relevant settings dispatches a ReloadMenu event so ordering channels can pick up changes:

  • Online Ordering updateevent(new ReloadMenu($locationId, ChannelOptions::OnlineOrdering->value)) (OnlineOrderingService).
  • Online Settings updateevent(new ReloadMenu($locationId)) (OnlineSettingsService).
  • Menu update / display-group reorderReloadMenu (and MenuUpdated for Uber Eats sync) from MenuService.

Snooze writes snoozed_until and propagates the pause state. Exactly how/when the storefront reflects each change is (storefront — not verified here).


Error Messages

These describe the intended customer-facing outcomes of back-office settings. The exact storefront copy is (storefront — not verified here); the cause column is grounded in back-office settings.

CauseSetting
Channel not subscribedNo active Online Ordering subscription for the location
Orders pausedsnoozed_until set (Snooze active)
Outside business hoursbusiness_hours / online-ordering custom availability + cut-offs
Restricted / temporarily closedrestricted_dates / temporarily_closed_until
Slot fullorder_capacity limits reached
Below minimum orderminimum_order_amount / pickup_minimum_order_amount
Outside delivery regiondelivery_region (postal codes / radius / max_distance)
Pickup/Delivery disabledtakeout.enabled / delivery.enabled
Payment failedStripe (or Square if connected)