F! Orders
Admin & Webmaster Documentation

Overview

F! Orders is a multivendor marketplace plugin built on top of WooCommerce, for restaurant ordering: pickup, dine-in, and delivery. It runs the Auani marketplace: restaurants apply, get approved, list a real menu, get paid through the platform, and can also run their own direct/white-label ordering on the side. There are no hidden upsells or paid tiers gating core functionality.

This documentation is written for the person operating the site: the admin or webmaster setting up the plugin for the first time. Follow it in order the first time through: Requirements, Installation, the Setup Wizard, then Settings.

Looking for how restaurants use their dashboard?

This page focuses on platform setup, configuration, and troubleshooting. The step-by-step guide for how a restaurant manages their menu, orders, delivery pricing, loyalty program, and more lives in a separate vendor-facing guide at /help. This page still summarizes each vendor-facing feature briefly, with a link out to the full walkthrough each time.

What makes this different from a DoorDash/UberEats reseller

The commission engine is channel-aware: a restaurant can join Auani as a discovery marketplace and also run their own direct ordering (their own QR code, Instagram bio link, an embeddable widget on their own site), and the platform only takes a commission on orders it actually generated. Orders a restaurant brought in themselves are 0% by default. See Commission Channels.

Two types of users

UserWhat they do
Restaurants (vendors)Apply through a vetting form, get approved (or auto-approved), then manage their shop from a front-end dashboard: menu items, orders, earnings, payouts, messages, and profile settings.
GuestsBrowse a restaurant's public menu page, order for pickup/dine-in/delivery, and message the restaurant directly. No account required to order or to message.

A few behaviors worth knowing

BehaviorWhat it means for you
Extends WooCommerce, doesn't replace itMenu items are native WC products, categories are native product_cat, modifiers extend WC's own cart mechanism, stock/purchasability is WC's own native system. This plugin adds a thin layer on top, not a parallel one.
Fails loud, not half-bootedIf a required plugin file is missing, F! Orders stops loading and shows an admin notice instead of running in a partially-working state. This plugin moves real money, so a visible stop is safer than a silent partial failure.
Product limits enforced server-sidePlan tiers limit how many menu items a restaurant can have published at once, enforced on the native product save hook regardless of how the request arrives.
"86'd" is a daily flag, not permanentMarking an item unavailable ("86'd") lasts only through the end of that day, then it's automatically orderable again - no restaurant action required to un-86 it tomorrow.

Requirements

  • WordPress 6.0+
  • PHP 8.1+
  • WooCommerce 7.2+ (required; F! Orders will not load without it active), tested up to 10.6
WooCommerce is a hard dependency. If WooCommerce is deactivated, F! Orders shows an admin notice and stops loading entirely rather than running in a broken state. Reactivate WooCommerce to restore F! Orders.

Installation

1

Confirm WooCommerce is active first

F! Orders hooks into WooCommerce's product, cart, checkout, and order systems. Install and activate WooCommerce before activating F! Orders.

2

Upload & Activate

Upload the plugin folder to /wp-content/plugins/ or install via the WordPress plugin screen, then activate.

3

You'll land in the Setup Wizard automatically

On first activation you're redirected straight to the setup wizard (F! Orders Setup) to configure Stripe, commission bands, plans, and vendor workflow before anyone can apply. See First-Run Setup Wizard.

If activation drops you on a blank "not allowed to access this page" screen: that's a WordPress permissions-message page, not necessarily a permissions problem - it also appears when a redirect points at a page slug that isn't registered. If you land there, go to your /wp-admin dashboard and open F! Orders from the sidebar directly. The wizard and every settings tab are reachable from there regardless.

Technical Architecture

F! Orders is built to fail loudly rather than silently, since it moves real money between guests, the platform, and restaurants.

  • Fail-fast module loading: every module (roles, products, commissions, payouts, delivery, dashboard, etc.) is required in a strict load order. If any single file is missing, boot halts immediately with an admin notice. No controllers, rewrite rules, or cron jobs register in that half-configured state.
  • Order↔vendor mapping via line-item meta, not sub-orders: one WooCommerce order per checkout regardless of how many restaurants are in the cart, avoiding the Dokan/WCFM child-order pattern that multiplies database rows and breaks native WooCommerce reports.
  • Channel-aware commission ledger: tiered rate bands applied per item price for marketplace-discovered orders, a separate flat (default 0%) rate for direct orders, with a configurable holding period before commissions are released for payout via a scheduled cron job. The rate is locked in at order time and never recalculated retroactively.
  • Payouts: Stripe Connect destination charges to restaurants. A separate, independent Stripe Billing integration (not Connect) handles restaurants paying for their own plan tier.
  • Delivery dispatch: a provider-agnostic dispatcher (FO_Delivery) with active adapters for DoorDash Drive and Uber Direct - entirely optional; a restaurant with no provider configured simply doesn't see delivery as a checkout option. A third adapter, Grubhub Direct, exists as scaffolding in the codebase but isn't registered - see Delivery Providers.
  • Uninstall safety: deactivating never deletes anything. Deleting the plugin only wipes commission, payout, and message data if you've explicitly opted in under Settings → Data & Uninstall. Product data, order data, and user data are never touched by the plugin's own uninstall routine.

First-Run Setup Wizard

Shown automatically on first activation, and reachable any time afterward from a banner link until all four steps are complete. Once complete (or manually dismissed) it no longer appears in the sidebar.

Menu sections need no separate setup. Menu categories (Appetizers, Entrees, Drinks...) are managed as regular WooCommerce product categories under Products → Categories - there's no separate mapping step in the wizard for this.
1

Stripe credentials

Publishable key, secret key, Connect Client ID, and webhook signing secret. Required before any restaurant can connect their own Stripe account and get paid. The wizard's own Stripe step now walks through each of these inline, including exactly which option to pick in Stripe's key-creation dialog and which event-destination scope the webhook needs. See Settings → API Config for the full guided walkthrough and the deep link to Stripe's Connect settings.

2

Commission structure

At least one marketplace commission band must be configured. See Settings → Commission.

3

Plans

At least one plan tier must exist so newly approved restaurants have a tier to be assigned to. Plans gate location count and marketplace commission rate, not menu size. See Settings → Plans.

4

Vendor workflow

Decide whether applications auto-approve and whether new menu items require moderation before publishing. See Settings → Vendors.

You don't have to finish in one sitting. A progress bar tracks completion (e.g. "2 of 4 complete"), and each step is independently save-able. The wizard reappears until all four are done, or until you dismiss it manually.

Settings → Commission

Found at F! Orders → Settings → Commission. The marketplace commission rate a restaurant actually pays is set per plan tier on Settings → Plans - this tab is just the holding period, plus an optional advanced fallback (below).

The holding period: the number of days after a sale before that commission is released and made eligible for payout. This gives a window for refunds/disputes to surface before money moves to the restaurant.

Consolidated 2026-07-19 (fourth pass): this tab used to show a full price-based Commission Bands editor as if it were a second, independent way to set the rate, duplicating the per-plan rate on Settings → Plans. It's now a collapsed "Advanced: price-based fallback rate" section, only reachable and only relevant for a plan tier that deliberately leaves its Commission Rate blank. Every default tier sets its own rate, so these bands go unused out of the box.

Settings → Plans

Define plan tiers: a slug, a label, a location limit (-1 means unlimited), a marketplace commission rate (blank means "use the price-based fallback bands on Settings → Commission instead"), and an optional Stripe Price ID. A tier with a Price ID lets restaurants subscribe to it themselves through Stripe Checkout and manage it via the Stripe Customer Portal. Tiers without one can only be assigned manually from the admin side.

Plan philosophy (redesigned 2026-07-19): no plan gates on features or product count - every restaurant gets the full feature set regardless of tier. The two real levers are how many locations a restaurant may register and what marketplace commission rate they pay. Default tiers: Free (1 location, 10%), $300/mo (unlimited locations, 5%), $3,000/yr (unlimited locations, 3%) - all editable, these are just a starting point, not a fixed business model.

There is no product/menu-size limit field on this screen at all - it was removed from the plan schema entirely (not just defaulted to unlimited) since it has no bearing on either lever above. The location limit and commission rate are enforced server-side: adding a location past the plan's limit is blocked in the dashboard's Locations tab, and the commission rate is read at order time by FO_Commission_Rates::resolve(), ahead of the price-based fallback bands.

Locations (multi-location restaurants)

A restaurant can register more than one physical location - each with its own address, weekly hours, and menu - while keeping one Stripe/payout account, one plan, and one public page (an in-page picker lets a guest switch between locations). How many a restaurant may register is set by their plan's location limit - see Settings → Plans.

Restaurants manage their own locations - hours, mobile/rotating spots for food trucks, and seasonal pause status - from their own dashboard. Full walkthrough: /help.

Admin-relevant detail: whatever address is live for a location is frozen onto an order the moment it's placed (_fo_pickup_address_snapshot) - the same "lock it at order time" principle used for the commission rate. This matters for delivery dispatch, which needs a stable pickup address.

Settings → Vendors

Two switches that shape the entire onboarding workflow:

  • Auto-approve applications: new restaurant applications are approved immediately with no manual review step.
  • Product moderation: new menu items from restaurants are held as pending until an admin approves them, rather than publishing immediately. When enabled, a Review Products page appears in the sidebar with a pending-count badge.

Settings → Categories

The global business category a vendor picks when applying - Pizzeria, Bakery, Food Truck, and so on. Separate from cuisine (Mexican, Peruvian, ...), which a vendor sets themselves on their profile - a vendor picks one of each, not one gated by the other. Also separate from the menu category set (Appetizers, Entrees, Dessert, ...) a vendor assigns to individual products - see the callout under Products (Menu Items) for how business category and menu category now interact.

Add, remove, or relabel categories freely; every category here is usable the moment it's added, since all of them run on the same menu/ordering system this plugin already builds - there's no separate "vertical" to build out first. Seeded with 27 defaults on install (Restaurant & Bar, Bakery, Food Trucks, Cafe & Coffee Shop, Pizzeria, Bistro & Fine Dining, Fast Food & Quick Service, Deli & Sandwich Shop, Steakhouse, Seafood, BBQ & Smokehouse, Buffet, Breakfast & Brunch, Diner, Pub & Gastropub, Brewery & Taproom, Winery & Wine Bar, Distillery & Cocktail Bar, Juice & Smoothie Bar, Ice Cream & Dessert Shop, Donut Shop, Catering, Food Hall / Market, Ghost Kitchen / Delivery-Only, Vegan & Vegetarian, Halal, Kosher) - none of them privileged over another; "Restaurant & Bar" being the biggest category is a go-to-market fact, not something built into the list.

Vendor-set once, then admin-only. An applicant picks their category during the application wizard - it's a required field, with a note in the wizard telling them it's locked after submission. From that point on, the only way to change it is the category dropdown next to each approved vendor on F! Orders → Vendors. It cannot be changed from the vendor's own dashboard.

Settings → Payouts

Sets the minimum payout amount. Restaurants cannot request a payout below this threshold, which keeps Stripe transfer fees from taking too large a cut of small balances.

Settings → API Config

Five independent integrations live under this one tab as a row of cards - two required for the platform to actually function (Stripe Connect, and Google Places if address verification is wanted), and three optional add-ons (Google Sign-In, a Google Business Profile connection layer that now powers a free "Google Verified" profile badge - see below - and Delivery for Uber Direct courier credentials).

API Config
Google Places
Stripe Connect
Google Sign-In
Business Profile Verified Badge
Delivery

Google Places

Address autocomplete + server-side re-verification for the restaurant application's Location step. An applicant picks their address from Google's own suggestions; the address that actually gets stored is re-fetched from Google server-side rather than trusted as typed, so a hand-edit after selecting a suggestion (or a browser autofill clobbering a field) can't reintroduce a typo. The API key is never sent to the browser - suggestions are proxied through a same-origin AJAX endpoint.

Stripe Connect

Marketplace payouts. Restaurants connect their own Stripe account via OAuth; guest orders route to them as destination charges, minus the platform commission. This is separate from Stripe Billing (vendor plan subscriptions), which uses the same Stripe account in its plain, non-Connect capacity - see Settings → Plans.

The guided setup walks through Connect step by step: creating/using a Stripe account, registering as a Connect platform, copying API keys (including which option to choose when Stripe asks how the key will be used), getting your Connect Client ID, adding the webhook endpoint, then pasting all values in and testing the connection before sending any restaurant through the Connect flow. As of the current version, this walkthrough has been verified directly against Stripe's live dashboard rather than written from memory of an older Stripe UI.

Direct link to Stripe's Connect OAuth settings (where the Client ID and redirect URI live): dashboard.stripe.com/settings/connect/onboarding-options/oauth.

The marketplace webhook needs "Connected accounts" scope, not the default. When creating the webhook destination in Stripe's Workbench (Developers → Webhooks → "+ Add destination"), Stripe defaults "Event destination scope" to "Your account." The events F! Orders needs there (account disconnects, payment failures, refunds, reversed transfers) all happen on a vendor's own connected Stripe account, not the platform's - so this must be switched to "Connected accounts," or the webhook will look configured but silently never receive anything. The in-plugin wizard calls this out explicitly; it's easy to miss if configuring the webhook by hand outside the wizard.
A WooCommerce Stripe payment gateway is also required for guest-facing checkout itself (separate from Connect). This tab detects which gateway is active and tells you exactly what, if anything, still needs configuring for destination charges to work with it.

Google Sign-In

Lets a restaurant applicant sign up using their own Google account instead of a typed password. Google verifies their identity and email, and the application form skips its separate email-confirmation step for that applicant. A separate credential pair from Google Places above - Places uses a server-side API key, Sign-In needs a full OAuth 2.0 Client ID/Secret registered as a web application in Google Cloud Console.

The settings screen walks through this directly rather than assuming familiarity with Google Cloud Console: setting up the OAuth consent screen, then creating the actual Client ID/Secret, each with a direct link to the exact page rather than just naming a destination and leaving the path to it up to you. Google reorganized OAuth consent screen setup into what it now calls the "Google Auth Platform" (Branding / Audience / Data access / Clients) during 2025-2026, so older third-party guides describing the previous "APIs & Services → OAuth consent screen" menu path no longer match what's actually on screen - this walkthrough is written against the current layout.

Requests the Business Profile scope, but doesn't use it. Google Sign-In's authorization request includes the business.manage scope alongside the identity scopes it actually needs, so an already-approved Cloud project doesn't force every applicant to re-consent later if that access is ever wired up. Nothing today reads or stores that grant - the access token from this flow is used once, to fetch the applicant's name and email, then discarded. See Business Profile below for the connection this scope is actually meant for.

Business Profile

Powers a free, automatic "Google Verified" badge on a vendor's public profile (added v1.0.36) - the connection mechanism existed since v1.0.22 with nothing built on top of it; this is its first real feature. Safe to configure now, and just as safe to leave blank; nothing else depends on it.

Deliberately built as a per-vendor connection, never one shared grant. Google's Business Profile permission is account-wide - whichever Google account authorizes it can see everything that account manages. An operator who personally manages several vendors' listings as a side service (a real, common setup) using that same account to authorize this connection would risk exposing every vendor it manages through a single grant, not just the one vendor in question.

So it never works that way. The Client ID/Secret configured here are just an app registration - like a "Sign in with Google" button, holding them grants no access to anyone's data by themselves. The actual authorization always happens per vendor, from F! Orders → Vendors: each approved vendor gets a Business Profile button that either connects directly (only meaningful if you personally happen to already be signed into that vendor's own Google account) or generates a one-time link for the vendor to open on their own device and connect with their own account - the normal path. That link resolves to an auto-created /gbp-connect/ page with a plain-language explainer shown before the Google redirect.

Setup mirrors Google Sign-In above - OAuth consent screen, then Client ID/Secret - with two differences: the consent screen only needs the business.manage scope (reuse the same consent screen as Sign-In if it's on the same Cloud project, rather than setting up a second one), and two additional APIs must be enabled in the Library first: My Business Business Information API and My Business Account Management API. That's a Google approval step, not just a settings toggle, and can take longer than the rest of this setup.

What the badge actually claims, and why it's free. "Google Verified" means exactly one thing: this business's manager completed the business.manage OAuth grant, proving real Business Profile access - not "the platform vouches for this business" (that's the separate, admin-assigned "Verified" badge under Vendors → Badges, a different signal entirely, deliberately never merged with this one). It's computed live from the connection status on every profile render, never stored, so it can't drift stale if a vendor revokes access from Google's own side. It's free and always will be - the OAuth connection itself costs nothing to grant. Reading or writing actual Business Profile data (hours, posts, review replies, insights - the kind of thing F! Insights already does at agency scale) is a separate, still-unbuilt, paid tier of capability; this badge is intentionally the free half of that story, not a preview of the paid half.

Delivery

Platform-wide Uber Direct credentials: Client ID, Client Secret, and Customer ID from Uber's developer dashboard, entered once here rather than per restaurant - the same reasoning as Stripe Connect and Google Business Profile above. This is what powers a real courier dispatch and, since v1.0.23, the checkout-time delivery fee itself (see Delivery Fee). Uber's own merchant approval is a separate step from this settings screen and can take longer than the rest of setup.

Settings → Messaging

Controls whether guests can message restaurants directly, and whether non-logged-in visitors can send a message (with name + email required).

Settings → Notifications

Four independent groups of email behavior:

Application alerts

An immediate email to you (or a chosen address) whenever a new restaurant application comes in.

Restaurant onboarding nudges

Automated follow-up emails sent directly to restaurants who stall during setup: day 3 after approval if they haven't listed any menu items yet, and day 7 if Stripe still isn't connected. Each nudge sends once per restaurant and is skipped if they've already completed that step.

Admin restaurant digest

A weekly summary email covering pending applications, recently approved restaurants and their setup status, and pipeline totals.

Email voice & appearance

Customizable email templates (application received, welcome/approved, rejected, day-3 nudge, new sale, low stock) with token substitution, plus shared appearance controls: logo, header color, reply-to address, footer tagline, and an optional CTA button on restaurant-facing emails.

Settings → Pages

Every page this plugin auto-creates and tracks by ID, in one place: its live URL, a direct wp-admin edit link, and a dropdown to reassign it to any other existing published Page. The Vendor Dashboard ([ fo_vendor_dashboard ]), Vendor Apply ([ fo_vendor_apply ], at /vendor-apply/), and Businesses Directory ([ fo_vendor_directory ], at /businesses/) pages are the ones that matter for day-to-day operation.

Note: earlier versions auto-created Gallery, Moniker, and "Moniker Brands Directory" browse pages; these are no longer created. If any still exist as orphaned Pages from an older install, they can be trashed from wp-admin → Pages.

Settings → Data & Uninstall

Deactivating the plugin never touches your data. Deleting it from the Plugins screen only wipes anything if you've explicitly checked the opt-in box here.

Always kept, regardless of this settingOnly removed if you opt in
Product meta on WooCommerce products, order meta, user meta on restaurants. All of this belongs to the site, not the plugin.Commission ledger, payout queue, message tables, all plugin options, and the vendor role itself.
Danger zone: checking "Wipe on uninstall" means deleting the plugin permanently drops the commission ledger, payout queue, and message tables. Leave unchecked to keep everything intact if the plugin is ever removed.

Admin Page: Vendors

Found at F! Orders → Vendors. The full restaurant roster and application queue in one place: pending applications awaiting review, approved restaurants with their current plan and status, and suspended accounts. Approve or reject pending applicants directly from this screen. Rejection supports an optional note that's appended to the rejection email.

Each approved restaurant's row also has: a Plan dropdown to reassign their tier, a Category dropdown (the business category from Settings → Categories - the only place it can be changed once a vendor has applied), a Badges button to toggle platform badges (New, Top Rated, Verified) shown on their public card, and a Business Profile button (dormant infrastructure, see Settings → API Config) showing that vendor's Google Business Profile connection status and a link to send them to connect it themselves.

Admin Page: Analytics

Found at F! Orders → Analytics. Operator-facing platform metrics: gross merchandise value (GMV) this month vs. last month, commission revenue (split by marketplace vs. direct channel), restaurant counts by status (active / pending / suspended), and the top restaurants by revenue this month. Cached for up to 6 hours between sales to keep the page fast.

Admin Page: Payouts

Found at F! Orders → Payouts. The payout queue: which restaurants have a released, payable balance, the status of any in-flight Stripe transfer, and payout history. This is the operational counterpart to Settings → Payouts.

Admin Page: Vendor Reviews

Found at F! Orders → Vendor Reviews. Added in v1.0.24. The moderation queue for guest reviews of a restaurant: 1-5 stars with an optional short text description, one per guest per restaurant, open only to a guest with at least one completed order from that restaurant. Every new or edited review lands here as pending before it's shown on the restaurant's public profile - approve or reject each one, with a direct link back to the order that qualified the reviewer. This plugin no longer has a vendor-to-vendor "peer review" concept; a restaurant's rating only ever comes from its own guests.

Admin Page: Review Products

Only appears in the sidebar when Product moderation is enabled under Settings → Vendors. Lists every menu item currently pending review, oldest first, with Approve (publish) and Reject (return to draft, with an optional note back to the restaurant) actions.

Admin Page: Debug Log

Found at F! Orders → Debug Log. Structured internal logging for activation events, boot failures, controller errors, and other operational events. This is the first place to check if something isn't behaving as expected.

Tip: if you ever hit a blank or blocked admin screen right after activating or saving settings, check here first. A logged boot-halt entry means a plugin file is missing. No entry at all points toward a page-navigation issue instead (see the callout under Installation).

Admin Page: Export Data

Found at F! Orders → Export Data. A CSV export for platform-level records. A restaurant's menu/catalog data itself already exports through WooCommerce's own Products → Export tool. This page covers what's specific to F! Orders.

Vendor Dashboard ( [ fo_vendor_dashboard ])

The front-end dashboard restaurants use day to day, placed on a page via the [ fo_vendor_dashboard] shortcode (created automatically on activation). No wp-admin access is required or granted for daily use - an approved vendor's wp-admin access is limited to the native Products screens only (see User Roles).

Tabs: Overview, Products, Modifier Libraries, Orders, Earnings, Payouts, Messages, Analytics, Profile Settings. The full tab-by-tab walkthrough - what a restaurant sees and does on each one - lives in the vendor-facing guide: /help.

Products (Menu Items)

Restaurants list and manage menu items using the native WordPress product editor, not a custom front-end form - a deliberate architecture choice (an earlier custom product form was removed in favor of extending WooCommerce's own editor via meta boxes and save hooks). A restaurant's access to wp-admin is trimmed to exactly this: the Products list, Add New, and editing their own products. Menu-item detail: /help.

Modifier Libraries

See /help for the vendor-facing walkthrough of modifier groups and shared modifier libraries.

Orders

Every order containing the restaurant's items, with a Kitchen Status card once an order is accepted. See Fulfillment & Order Tracking and /help.

Earnings

The commission ledger from the restaurant's side. See /help.

Payouts

Stripe Connect account status, available balance, minimum payout threshold, and payout history from the restaurant's own point of view. See /help.

Messages

Guest-to-restaurant messaging, when enabled under Settings → Messaging. See /help.

Analytics

The restaurant's own performance view, separate from the platform-wide admin Analytics page. See /help.

Profile Settings

Restaurant-controlled profile and account settings. The business category (Pizzeria, Bakery, ...) is not here - see Settings → Categories for why. Full field list: /help.

Public Restaurant Menu (summary)

Every restaurant gets exactly one public page, at /business/{slug}/ (or embeddable via the [ fo_vendor_profile ] shortcode). A restaurant with more than one location gets an in-page picker rather than a page per address - see Locations.

Old /restaurant/{slug}/ links no longer resolve. The URL prefix changed to /business/{slug}/ in v1.0.2 (see Roadmap & Changelog). This is a plain rewrite-rule change, not a page-slug rename, so there's no old-slug redirect - a bookmark, QR code, or social bio link still pointing at the old path will 404. Worth knowing when a restaurant reports their page is "broken."

Full menu-page and cart behavior: /help.

Modifier Groups & 86'd Items (summary)

Per-item option groups ("choose your protein," "extra guac") and the "86'd today" unavailable-until-service-resets flag, both set from the item's own product editor. Full explanation: /help.

Shared Modifier Libraries (summary)

A vendor-level library of reusable modifier groups a restaurant builds once and attaches to any number of dishes. Full explanation: /help.

Fulfillment & Order Tracking

Pickup, dine-in, and delivery are chosen by the guest on WooCommerce's own native checkout page, alongside a fulfillment-timing choice (ASAP by default, "Schedule for later" as a secondary choice). Delivery is only offered if every item in the cart supports it and a courier provider is actually configured for that restaurant (see Delivery).

A food-specific kitchen-stage tracker (received → preparing → ready → completed) is layered on top of WooCommerce's own order status - it never touches commission release, courier dispatch, or payout timing, which all still key off native WooCommerce order status exactly as before. Restaurant-side usage: /help.

Known limitation: the checkout fulfillment field currently assumes a single restaurant per cart. A marketplace cart spanning multiple restaurants with different fulfillment needs isn't solved yet.

Order escalation (unacknowledged-order fallback)

The kitchen-stage tracker's received stage is set automatically the moment an order is paid - it does not mean a human at the restaurant has actually seen it. Historically the only signal a restaurant got on a new order was one transactional email, with no retry and no visibility for the platform if that email was missed. As of v1.0.25, if an order is still sitting at received 10 minutes after acceptance - i.e. nobody has tapped "Start preparing" - two things happen: the vendor gets a second, more urgent email, and the platform admin gets a separate alert containing the vendor's contact details and a direct link to the order in wp-admin, so a human can intervene (call the restaurant) if the automated channel didn't get through. Both the vendor's Orders list and the order-detail screen show a "Needs attention" flag on any order that reached this state.

This is a floor, not a substitute for a POS. It's a cheap, always-on fallback that still applies to every order regardless of whether a restaurant has connected a POS (see POS Integration) - a failed POS push means the POS never actually showed the order to anyone, which is exactly the situation this escalation exists to catch. A genuine kitchen-printer integration (e.g. Star CloudPRNT or Epson ePOS-Print, which push a print job straight to a networked receipt printer with no local software or POS required) remains on the roadmap, not yet built.

Commission Channels

This is the core economic differentiator of the platform, and it's a completely separate axis from fulfillment (pickup/dine-in/delivery):

ChannelHow it happensCommission
MarketplaceA guest discovered the restaurant by browsing Auani. This is the default channel whenever there's no signal otherwise - the safe, commissioned default, never an accidental free ride.The restaurant's plan commission rate if their tier sets one (the default for all three tiers); otherwise falls back to the price-based bands under Settings → Commission. A per-restaurant override, set by the admin, beats both.
DirectA guest ordered through the restaurant's own embedded widget, QR code, or bio link - see Direct-Order Widget. Because the marketplace catalog never routes guests through that endpoint, there's nothing to spoof: any order landing there is definitionally direct.A flat platform-wide rate, 0% by default. Not a purchasable tier - an operator-level constant.

Whatever rate applies is locked into the commission ledger at the moment the order is placed and never recalculated later, even if bands or the direct rate change afterward. This is a financial invariant.

A vendor can't check out an order with no resolvable commission rate. If a vendor's plan leaves its rate blank and the price-based fallback bands on Settings → Commission don't cover the order total, checkout is blocked rather than silently defaulting to 0%. Make sure every plan tier either sets its own rate or that the fallback bands cover the full price range.

Embeddable Ordering Widget

Added in v1.0.29. A restaurant pastes one script tag onto their own website - any platform, not just WordPress - and gets a floating "Order online" button that opens their live menu in an overlay and hands off to checkout. Chosen over a WordPress-only companion plugin for two reasons: it needs no menu-sync mechanism at all (the widget always renders live from this same platform data, so there's never a second copy of a menu to drift out of date), and it needs no new payments architecture (checkout still completes through the existing, already-audited flow).

AttributeHow
Where checkout completesThe widget hands off to /order-direct/{slug}/ (the same server-side handler described below) via a real top-level browser navigation, not a background request - a widget embedded on a third-party domain making a background call to auani.com can silently break under Safari's and Chrome's third-party-cookie restrictions; a real page navigation doesn't have that problem.
Embed methodAn iframe pointed at auani.com/order-widget/{slug}/, not a script that injects markup directly into the host page. Full style isolation in both directions - the host site's CSS can never break the widget, and the widget's CSS can never leak into theirs.
SizingThe iframe posts its real content height to the parent page, which resizes the overlay panel to fit - no wasted empty space on a short menu, no awkward internal scrollbar on a long one.
Scope for v1Single-location vendors. A multi-location restaurant's widget always targets their primary location; an in-widget location picker isn't built yet.

Installation snippet, edge-case handling, and the single-restaurant-cart rule are covered restaurant-side in /help.

White-label checkout

Added in v1.0.30. Settings → Plans has a White-Label Checkout toggle per tier - turn it on for a tier and every vendor on it gets checkout that stays entirely on their own site, instead of redirecting to auani.com to pay. No separate payment system was built for this: the widget requests first-party storage access to auani.com from inside its own iframe (the browser's standard Storage Access API, triggered by the checkout click itself), and then loads the exact same native checkout page - same Stripe processing, same commission logic - inside that iframe instead of breaking out to the top-level window. A vendor on a plan without the toggle is entirely unaffected and keeps the redirect-to-auani.com checkout.

Needs real cross-browser verification before high-volume use. Built correctly against the Storage Access API's current published specification, but this environment cannot click through an actual browser permission prompt to confirm the experience. Two specific things worth testing on a real site before leaning on this heavily: Safari shows a real permission prompt the first time (Chrome and Firefox can resolve silently after the first grant) - confirm it doesn't read as alarming on an actual restaurant's site; and that a granted permission carries over correctly when the widget navigates from the menu to the checkout page within the same iframe, not just on a same-page reload.

Loyalty & Rewards (summary)

Added in v1.0.35. A punch card, not a coupon system: a guest who orders from a restaurant a set number of times earns one free menu item from a list the restaurant has approved. There is no discount-code or percentage-off mechanism anywhere in F! Orders - a deliberate choice, not a gap. There are no admin-level settings for this feature - it's configured entirely from each restaurant's own dashboard. Full setup and redemption walkthrough: /help.

Account required, same as Reviews. Progress is tracked against a real WordPress user ID, so guest checkout (no account) can't earn or redeem.

Delivery Providers

Pickup and dine-in are this plugin's priority - delivery is a genuinely optional secondary path. If no provider is configured for a restaurant, delivery simply isn't offered as a checkout option; everything else works exactly the same.

Three white-label courier provider adapters exist in the codebase: DoorDash Drive, Uber Direct, and Grubhub Direct. All three are doc-verified - built and checked against each provider's current published API - but honestly, none has been exercised against a real live sandbox call from this environment. They sit at three different levels of completeness, not one finished leg and two unfinished ones: Uber Direct has the most complete integration, including a real checkout-time fee wired all the way through (see Delivery Fee below), but the exact request/response field mapping for getting a quote and dispatching a delivery is still unconfirmed against a live call. DoorDash Drive has real, working authentication (as of v1.0.30) and can be set up and sandbox-tested today under Settings → API Config → Delivery, with the same field-mapping caveat as Uber Direct on top, no checkout-time fee wired up yet, and real (non-sandbox) dispatch additionally requires DoorDash's own production-access approval - see the callout below. A restaurant can pick their own provider if more than one is configured for their fulfillment; otherwise a platform-wide default applies. Credentials are entered once at the platform level - individual restaurants never hold API keys themselves, the same way Stripe Connect centralizes payment auth on the platform side while still crediting the right restaurant.

Confirm field mapping with a real sandbox call before relying on either provider's fee or dispatch in front of a real restaurant. Both Uber Direct's and DoorDash Drive's request/response shapes for quoting and dispatching a delivery are built exactly to each provider's documented spec, but neither has been run against a live sandbox credential from this environment - there's no network access here to do that. A single real test order per provider is worth doing before high-volume use.
DoorDash Drive: production access is gated by DoorDash, not by this plugin. DoorDash's own developer portal currently states production certification has no committed timeline. Sandbox setup (developer account, access key, the Delivery Simulator) is fully self-serve and safe to complete today - see the deep links in Settings → API Config → Delivery. Going live for a real restaurant depends on DoorDash approving a production-access request submitted from that same portal.
Grubhub Direct adapter is scaffolding, not selectable anywhere. Its method signatures and request shape exist in the codebase, but its authentication model hasn't been verified against a live account, it has no checkout-time fee wired up, and (as of v1.0.37) it's deliberately not registered as an active provider - there was no settings screen anywhere to configure it, so it sat in the provider list looking like a live option when it could never actually be turned on. It'll be re-registered once a real Grubhub partner agreement and settings screen both exist.

Delivery Fee

Added in v1.0.23. When a guest chooses Delivery at checkout, F! Orders requests a real, live quote from Uber Direct for that specific pickup-to-dropoff pair - never a flat or estimated number - and charges the guest their share of it as an itemized fee line, separate from the menu subtotal. The same quote is reused (not re-requested) when the courier is actually dispatched, so the price a guest saw at checkout is the price locked in with Uber.

The split

Each restaurant sets what percentage of that real fee the guest pays, from a 0-100% slider on their own dashboard, or a more granular order-size-tiered schedule (added v1.0.39) underneath it - the lowest tier of that schedule doubles as the restaurant's delivery minimum. Restaurant-side setup and the live dollar-math preview: /help.

Platform default tiers (Settings → API Config → Delivery)

Added v1.0.39. Any restaurant who never opens their own tier editor falls back to a platform-wide default schedule, set from Settings → API Config → Delivery → Delivery Fee Split — Platform Default. That screen has the same tier-row editor (order-size threshold + guest percentage, up to 8 rows) and the same adjustable-sample-fee live preview as the vendor-facing one, posting to fo_save_delivery_fee_tiers and stored in the fo_delivery_fee_tiers_default option (via FO_Delivery_Fee_Tiers::OPTION_DEFAULT). Leaving it empty preserves the pre-v1.0.39 behavior exactly: no platform-wide minimum, flat 100%-to-guest split, unless a restaurant sets their own slider.

How a restaurant's absorbed share is actually funded

Stripe destination charges split once, at the moment of charge - there's no clean way to claw money back from a restaurant's connected account afterward. So whatever a restaurant chooses to absorb is folded into the platform's Stripe application fee at that same charge-time split for orders that get an automatic destination-charge split; for orders that don't (a multi-restaurant cart, or a restaurant without Stripe fully connected, both of which fall back to the manual/Stripe-Connect-Transfer payout queue), the same amount is recorded as its own line in the commission ledger and comes out of that restaurant's next payout instead. Either way, it's funded from the same order-level split locked in at checkout - never a separate, later charge against the restaurant.

Delivery is still single-restaurant only. Matches the existing scope of delivery dispatch itself (see Delivery Providers above) - a cart spanning more than one restaurant never offers Delivery as a fulfillment choice, so there's nothing for this fee to split in that case.

POS Integration

Added in v1.0.28. A restaurant can optionally connect Square or Clover so an accepted order is pushed straight to their in-store point-of-sale system, automatically, at the same moment it's accepted. This is a stronger tier on top of (never a replacement for) the dashboard, email, and order-escalation notifications every restaurant already gets - it's tied directly to the same "an accepted order should never go unaccounted for" principle behind escalation, just one rung higher: the order shows up on the actual system the kitchen is already watching.

Built on the same one-interface-many-adapters pattern as the Delivery couriers (Delivery Providers) - one contract (FO_POS_Provider), swappable provider classes, so adding a future provider means one new adapter, not a redesign.

Restaurant-side connect flow (Settings → POS Integration, only shown once at least one provider is configured below): /help.

Platform setup (admin side)

Square and Clover both require a one-time developer app registered with each platform. Enter the resulting App ID and App Secret under Settings → API Config → POS, along with the exact redirect/callback URL shown on that screen (each platform's developer dashboard needs that URL registered before any restaurant can connect). A sandbox toggle is available for testing against each platform's sandbox environment before going live.

ProviderStatusNotes
SquareDoc-verifiedOAuth connect/refresh and order push are both built and checked against Square's current published API, but not yet confirmed against a live Square sandbox call from this environment - the same verification tier as Clover below, no self-serve production gate beyond that once confirmed.
CloverDoc-verifiedOAuth connect/refresh and order push are both implemented against Clover's current published API. Two specific details - the line-item quantity field and the token-refresh endpoint's exact parameters - are built to the documented shape but not yet confirmed against a live Clover sandbox call. Separately, going live for a real restaurant also requires the app to be submitted to and approved on the Clover App Market - a partner-approval step outside this plugin's control, the same shape as Toast's gate below, not just a sandbox pass.
ToastNot includedToast has no self-serve developer access at all - a formal Integration Partner application, a discovery call, and a signed partner agreement with Toast are required before any API credentials are issued, let alone code written against them. This is a business/partnership step outside the plugin itself, not an engineering gap.
Neither Square nor Clover has been run against a live sandbox call from this environment. Both are built exactly to each provider's documented request/response shape, but confirming the fulfillment object's exact nested fields needs one real sandbox order per provider before trusting either in front of a real restaurant. Clover carries a second, non-technical gate on top: Clover App Market approval.
A failed push is never silent. If sending an order to a connected POS fails (an expired connection, a POS-side outage, a network blip), it's logged and added as a visible order note on that order - and the order still goes through the same 10-minute unacknowledged-order escalation as if no POS were connected, since a failed push means the POS genuinely never showed the order to anyone.

Integration Hook: fo_order_accepted

The one stable, documented integration point this plugin exposes for a future POS system, kitchen display, or delivery partner not built directly into F! Orders. Fires once when a restaurant accepts an order (the order moves to "Processing"), before delivery dispatch and before the restaurant's own new-order notification email.

Deliberately modeled on a thin two-surface integration pattern: reading the menu is already free via WooCommerce's own native REST API, and this hook is the other half - "an order was just accepted." A third-party integration only ever needs those two things, not deep access to this plugin's internals.

Shortcodes

Every shortcode renamed in v1.0.3 still works under its old name too - the old tag is registered as an alias to the same renderer, so nothing already placed in a page builder breaks.

ShortcodeOld aliasDescription
[ fo_vendor_apply][ fo_restaurant_apply]The restaurant application form: business category, cuisine, vetting fields, location (with Google Places verification), and everything needed to route a new applicant into the Vendors queue.
[ fo_vendor_dashboard]-The full front-end restaurant dashboard. See Vendor Dashboard.
[ fo_vendor_profile][ fo_restaurant_profile]Embeds one restaurant's full public profile + menu inside a hand-built page-builder template (e.g. Divi Theme Builder), instead of only ever rendering through the default /business/{slug}/ URL.
[ fo_vendor_directory][ fo_restaurant_directory]A directory/roster listing of every approved restaurant, at /businesses/ by default - filterable by cuisine, with badges and catering availability surfaced on each card, sorted by distance from the visitor when browser geolocation is available (never required).
[ fo_browse_menu]-Every restaurant's published menu items in one cross-restaurant grid, auto-created on the /browse page. Now leads with a business-type tab bar above the grid (All types, plus one tab per business type with at least one approved vendor) - picking a type scopes the grid and narrows the category/dietary/vendor filters below it to that type; the category set itself stays the one shared, platform-wide list.

User Roles

RoleCapabilitiesNotes
fo_vendorread, upload_files, edit_posts, edit_published_posts, publish_postsRepresents a restaurant account. Kept minimal on purpose: no manage_woocommerce, no delete_posts (restaurants archive rather than delete), no edit_others_posts. wp-admin access is trimmed to the Products screens only; everything else (orders, earnings, settings) lives on the front-end dashboard. Status tracked separately as pending, approved, or suspended.

Legacy Surfaces & Known Issues

No open known issues at this time.

As of v1.0.29: the direct-order widget's per-location ownership check - previously only confirmed a submitted order's items belonged to the right restaurant, not to the specific location a slug identifies - is fixed. Now that the embeddable widget is live (see Embeddable Ordering Widget), every submission is validated against the vendor's actual location, not just the vendor.
As of v1.0.24: the "Restaurant Reviews" and "Peer Reviews" menu items this section used to describe as broken are resolved - Vendor Reviews is now a real, working moderation queue (see Admin Page: Vendor Reviews), and Peer Reviews (the leftover, always-empty menu entry) has been removed outright.

Testing Checklist

A practical run-through for verifying a fresh install or update before opening the site to real restaurants and guests.

1

Activation

Activate the plugin. Confirm you land on the Setup Wizard. If you land on a blocked-access screen instead, go to /wp-admin manually and open F! Orders from the sidebar (see the callout under Installation). Complete all four wizard steps.

2

Restaurant application → approval → first menu item

Submit a test application via [ fo_vendor_apply] - pick both a business category and a cuisine - approve it from Vendors, log in as that restaurant, and publish one menu item (with a modifier group) from the native product editor.

3

Public menu page

Visit /business/{slug}/. Confirm the item appears under the right category, "Choose Options" opens correctly, and adding to cart works both with and without modifiers selected.

4

A full test purchase, marketplace channel

Buy the test item as a guest via the public menu page. Confirm the order completes, a commission ledger entry appears under the restaurant's Earnings with channel = marketplace and the correct band rate applied, and that the guest-facing order tracker (see Fulfillment & Order Tracking) shows the expected stage and ETA.

5

Debug Log is clean

Check F! Orders → Debug Log for anything unexpected logged during the steps above.

Roadmap & Changelog

v1.0.39 - Vendor-configurable delivery minimum & order-size fee tiers
  • New: a restaurant can define their own ladder of order-size tiers for the delivery fee split (Dashboard → Settings → Delivery Fee Split → "Advanced: reward bigger orders with a lower delivery fee") - at or above a chosen order size, the guest pays a smaller share of the real fee and the restaurant covers more. The lowest tier's order size becomes that restaurant's delivery minimum; below it, Delivery isn't offered at checkout at all. See Delivery Fee.
  • New: a platform-wide default tier schedule (Settings → API Config → Delivery) applies to any restaurant who hasn't set their own. Leaving both empty preserves the original single flat percentage exactly as it worked before this release.
  • New: both settings screens show live dollar-math next to each tier against an adjustable sample fee, so the real-money effect of a percentage is visible before saving.
  • Fixed: a submitted 'delivery' choice on native checkout was never re-validated server-side against provider/item/minimum eligibility - a tampered or stale request could reach order creation despite Delivery never having been a legitimately offered option. Now re-checked at order-creation time and silently downgraded to pickup if invalid.
v1.0.38 - Demo data: loyalty, photos, live demo shopper account
  • The Debug screen's "Seed 5 DMV test vendors" button now seeds a profile photo and banner per vendor (generic stock placeholder photography - not real food photos, best-effort and non-fatal if the fetch fails) and enables loyalty on 2 of the 5 vendors, each with one menu item marked redeemable.
  • New: a fixed demo shopper account (demo_loyalty_customer) is seeded alongside them with punch progress already set - one vendor shows the "reward earned, redeem now" state, the other a mid-way progress bar - so the loyalty flow from Loyalty & Rewards can be demonstrated live without placing a single real order first.
  • Wipe (clear seeded/demo data) was extended to match: it also removes the seeded photo/banner attachments, any seeded loyalty rows, and the demo shopper account, so nothing this adds is left behind.
v1.0.37 - Scaling audit fixes
  • Fixed: the /businesses vendor directory and the /browse filter panel each ran an uncached, full-catalog query on every page view. Both now cache their result in a short-lived transient, invalidated explicitly on vendor approval/suspension, profile save, and product publish/trash - the two most-visited pages under real growth are the ones this fixes.
  • Fixed: admin CSV export silently dropped every row past 10,000 with no indication it happened. The export screen now shows a specific "showing most recent 10,000 of N" warning per table when it applies, and the same note is written into the CSV file itself as its first line.
  • Removed Grubhub Direct from the active delivery-provider list - it had no settings screen anywhere to configure it, so it was never actually selectable; its credential storage was also fixed to match every other provider's pattern for whenever it's rebuilt for real. See Delivery Providers.
  • Fixed: bulk CSV menu import fetched each row's image synchronously inside the main import request - large CSVs with many image URLs risked a timeout before the import finished. Image fetching now runs in a background pass right after import completes.
  • Removed a dead "artwork dimensions" compatibility shim and an unused per-line-item query that ran alongside it on every order-detail page view.
v1.0.36 - Google Verified badge (first feature on the Business Profile connection)
  • New: a free "Google Verified" badge on a vendor's public profile, automatic the moment their Business Profile OAuth connection (live since v1.0.22, unused until now) is active. No settings to configure beyond the existing Business Profile card - see Settings → API Config → Business Profile.
  • Computed live from the connection status on every page render rather than stored - it can never go stale, and vendors who connected years before this feature existed don't need any backfill to get the badge.
  • Distinct from the existing admin-assigned "Verified" badge (Vendors → Badges) on purpose: that one is a human platform judgment call, this one is a narrower, OAuth-proven claim. The two are never merged into one slug.
  • Architecture note: this plugin and F! Insights now have a documented, symmetric hook contract (fo_gbp_connected / fo_gbp_disconnected) either can build on without one plugin ever requiring the other to be installed - detect-and-enhance, not require-and-crash. Full Business Profile read/write (hours, posts, review replies, insights) stays a separate, paid capability - this badge is deliberately the free half.
v1.0.35 - Loyalty & Rewards (punch card)
  • New: punch-card loyalty program, replacing coupons as the platform's answer to repeat-visit incentives - a restaurant sets how many orders earn a reward and, optionally, a minimum number of days between redemptions, then approves specific menu items as the reward. No discount-code system exists anywhere in F! Orders; this was a deliberate choice against coupon-farm pressure on already-thin food & drink margins. See Loyalty & Rewards.
  • Progress is tracked per (restaurant, guest) and only for logged-in guests - same account-required precedent as Vendor Reviews.
  • Redemption reuses WooCommerce's own cart-pricing mechanism to zero the reward item's price, the same mechanism WooCommerce uses for coupons internally - no parallel pricing system.
  • Corrected this document's own POS and Delivery status language: Square, Clover, DoorDash Drive, and Uber Direct are now all described consistently as doc-verified (built and checked against each provider's published API) but not yet exercised against a live sandbox call from this environment - see POS Integration and Delivery Providers. Clover's remaining work now also explicitly includes Clover App Market approval, not just a sandbox pass.
v1.0.30 - White-label checkout, DoorDash Drive, location fix
  • New: white-label checkout for the embeddable widget is live for any plan tier with White-Label Checkout enabled (Settings → Plans) - a visitor never leaves the restaurant's own site to pay. Built on the browser's standard Storage Access API plus the existing native checkout, not a separate payment system. See Embeddable Ordering Widget. Needs real cross-browser verification before high-volume use - honestly flagged, not silently assumed solid.
  • New: DoorDash Drive as a second delivery provider, with real JWT authentication built against DoorDash's own current published reference implementation. Sandbox-ready today; real dispatch depends on DoorDash's own production-access approval, which currently has no committed timeline on their side. See Delivery Providers.
  • Fixed: the direct-order/widget checkout now validates every item against the vendor's specific location, not just the vendor - closes a gap that was harmless before the embeddable widget existed.
  • Every new setup step in this release links directly to the exact external page needed (DoorDash's developer portal, credentials page, and production-access form) - see Settings → API Config → Delivery.
v1.0.29 - Embeddable ordering widget
  • New: a restaurant can paste one script tag onto their own website - any platform, not just WordPress - and get a live ordering widget: a floating button that opens their real menu and hands off to checkout. See Embeddable Ordering Widget.
  • Chosen deliberately over a WordPress-only companion plugin: no menu-sync mechanism needed (always renders live from this same data, so there's never a second copy to drift out of date) and no new payments architecture (checkout still completes through the existing, already-audited flow).
  • Handles a restaurant being closed, an item going unavailable between add-to-cart and checkout, and an unpredictable embed space (narrow sidebar, crowded page) - reviewed against wireframes for all three before building.
  • New per-plan White-Label Checkout setting exists in Settings → Plans for a future release, but isn't wired up yet - every tier's widget checkout redirects to auani.com to pay today, honestly, until the third-party-cookie problem an inline checkout requires solving is actually tested cross-browser.
v1.0.28 - POS integration: Square and Clover
  • New: optional POS integration. A restaurant can connect Square or Clover from their dashboard, and every accepted order is pushed to their in-store system automatically - the strongest tier of "the restaurant actually knows about this order," layered on top of (never replacing) the existing dashboard/email/escalation notifications. See POS Integration.
  • Square and Clover are both built and checked against each provider's current published API (doc-verified), but neither has been run against a live sandbox call from this environment yet - see POS Integration for the exact caveats and, for Clover, its additional Clover App Market approval gate.
  • Toast is intentionally not included - it requires a formal partner application with Toast directly, outside what this plugin can do on its own.
  • A failed push never fails silently: it's logged and order-noted, and the existing unacknowledged-order escalation still applies exactly as if no POS were connected.
v1.0.27 - Bulk menu import (CSV)
  • New: a vendor can upload a CSV to add or update dozens of menu items at once from their product list screen, instead of hand-typing each one - identified as the single biggest onboarding-friction point for a restaurant with an existing menu. See Bulk Menu Import (CSV) for the exact column format and a sample.
  • Re-uploading a corrected file updates items matched by name rather than creating duplicates, and every field is written through the same save path as the native product editor, so an imported item behaves identically to a manually created one.
  • Modifier groups are intentionally not part of the CSV in v1 - attached afterward via the existing shared Modifier Library, which is what that reuse mechanism was built for.
v1.0.26 - Money-path audit fixes
  • Fixed a real double-payment bug: destination-charge orders (Stripe auto-transfers the vendor's share at checkout) were marked with the same ledger status as money genuinely awaiting a payout, so a vendor requesting a payout after such a sale was paid a second time for the same order. That money now gets its own status ('paid_direct') that still counts as earned but can never re-enter the payout queue.
  • Fixed a checkout-breaking edge case: a small order where a vendor absorbs 100% of a real, expensive courier quote could push the Stripe application fee above the order's actual charge amount, which Stripe rejects outright. The fee is now capped at the charge total, with any resulting platform-absorbed shortfall logged and left as a visible order note rather than crashing checkout or vanishing silently.
  • Fixed vendor "Lifetime Earnings" occasionally shrinking after a payout completed - it now correctly sums every commission row regardless of status.
  • These came out of a full audit of the checkout/commission/payout path, vendor operations and access control, and the guest-facing feature set - see below for what else was checked.
v1.0.25 - Order-acknowledgment escalation
  • New: the floor-level fallback for "a paid order should never go unaccounted for." If an order is still sitting at the kitchen tracker's "received" stage 10 minutes after acceptance (nobody tapped "Start preparing"), the vendor gets an urgent re-alert email and the platform admin gets a separate notification with the vendor's contact info and a direct order link - a human backstop if the automated channel alone didn't get through.
  • Both the vendor Orders list and order-detail screen now show a "Needs attention" flag on any order that reached this escalated state.
  • Explicitly scoped as a cheap always-on floor, not a kitchen-printer or POS integration - see Order escalation for the tiers considered (Star CloudPRNT/Epson ePOS-Print for printer-only, Chowly/Deliverect/Otter/ItsaCheckmate for real POS middleware) and why those remain roadmap items rather than built now.
v1.0.24 - Restaurant reviews, and peer reviews removed
  • New: guest reviews of a restaurant - 1-5 stars with an optional short text description. Requires a completed order with that restaurant to submit one, one review per guest per restaurant (a second submission edits the first), and every new or edited review is moderated (F! Orders → Vendor Reviews) before it's shown publicly. Simpler than the multi-category (Service/Ambiance/Food & Drinks) design sketched earlier - that depth is deferred, not dropped.
  • Removed outright: peer reviews (vendor-to-vendor ratings). Already dead code from a prior removal pass - a guarded, always-empty menu entry and two no-op handlers were left behind; both are now gone. This plugin only ever surfaces a guest's review of a restaurant.
v1.0.23 - Live Uber Direct delivery fee, split between guest and restaurant
  • Choosing Delivery at checkout now charges a real, live Uber Direct quote for that specific order as an itemized fee - never a flat or estimated number. The same locked-in quote is reused when the courier is dispatched, so the price a guest saw is the price paid.
  • New restaurant dashboard setting, Settings → Delivery Fee Split: a 0-100% slider for how much of that real fee the guest pays vs. the restaurant absorbs, defaulting to 100% (guest pays) until a restaurant changes it. Deliberately not a menu-price markup - see Delivery Fee for the reasoning.
  • Whatever a restaurant absorbs is funded correctly depending on how the order was paid: folded into the platform's Stripe application fee at charge time for destination-charge orders, or recorded as its own commission-ledger line for the manual/Stripe-Connect-Transfer payout path.
  • New Delivery card under Settings → API Config for Uber Direct's platform-wide credentials (Client ID, Client Secret, Customer ID) - previously read from the database with no admin screen to actually set them.
  • Fixed: choosing "Delivery" on the checkout fulfillment radio didn't refresh the visible order total until some unrelated field happened to trigger it.
v1.0.22 - Dormant per-vendor Google Business Profile connection layer
  • New infrastructure (not yet used by any live feature): a full OAuth connection flow for a vendor's Google Business Profile, built so a future feature that reads or updates it starts from a correct per-vendor access boundary rather than a shared-identity shortcut. See Settings → API Config → Business Profile for the reasoning.
  • Each vendor connects via a one-time link they open and authorize themselves (new auto-created /gbp-connect/ page) - never through the platform operator's own Google account, even if that operator separately manages the vendor's listing as a marketing service.
  • New Business Profile button per approved vendor on F! Orders → Vendors, showing connection status and a way to send the vendor a connect link.
  • Fixed a stray stale domain link on the Vendors admin page's help banner (immibrand.com/docs, left over from before this documentation moved to auani.com/docs).
v1.0.21 - Google Sign-In walkthrough and API Config layout fix
  • The API Config card row could wrap its last card onto its own line at narrower admin widths (a flex-wrap side effect). Switched to a fixed grid so every card always shares one row.
  • Google Sign-In's setup instructions rewritten into a direct, step-by-step walkthrough for Google's current OAuth consent screen UI - previously it just said to paste the redirect URI "in Google Cloud Console" with no link and no path to get there. See Settings → API Config → Google Sign-In.
v1.0.20 - Documentation and deep-link accuracy pass
  • Fixed a broken webhook path documented in the plugin readme (written as fo/v1, actually fm/v1) and refreshed its feature list to cover everything below. Fixed stale pre-1.0.2/1.0.3 URLs (/restaurant-apply/, /restaurant/{slug}/, /restaurants/) still present in the restaurant-facing getting-started guide.
v1.0.16 - 1.0.19 - Stripe Connect walkthrough verified against Stripe's current UI
  • The guided Stripe setup (both the first-run wizard and Settings → API Config) was rewritten to match Stripe's current key-creation dialog and Workbench webhook flow, in place of an older walkthrough that had drifted out of sync with Stripe's own UI changes.
  • Corrected the marketplace webhook's subscribed events - the previous list didn't match any event the webhook handler actually processes, meaning payment failures, refunds, and vendor Stripe disconnections were going unhandled even with the webhook "configured." See the callout under Settings → API Config for the current event list and the "Connected accounts" scope requirement.
v1.0.14 - 1.0.15 - Category narrowing by business type, /browse leads with type tabs
  • The locked menu category set (Appetizers, Entrees, Dessert, Add-ons, Drinks, Specials, Combos) is unchanged, but a vendor's product-category picker now narrows to a relevant subset based on their business type - a bakery isn't offered Appetizers or Entrees. See the callout under Products (Menu Items).
  • The cross-vendor /browse page now leads with a business-type tab bar above the grid, rather than category being the only way to narrow results. See Shortcodes.
v1.0.13 - ASAP-first fulfillment and kitchen-stage order tracking
  • Checkout's fulfillment field defaults to ASAP with a live "ready in about N minutes" estimate; "Schedule for later" is now an explicit secondary choice instead of an unstyled equal-weight option.
  • Added a kitchen-stage tracker (received / preparing / ready / completed) layered on top of WooCommerce's own order status - vendor-facing stage-advance buttons on the Orders tab, and a guest-facing tracker on the thank-you and account order pages. See Fulfillment & Order Tracking.
v1.0.12 - Shared modifier libraries
  • A vendor can build a reusable modifier group once (e.g. "Proteins") and attach it to any number of dishes, with per-dish overrides for price or availability, instead of retyping the same options into every dish's own modifier group. New dashboard tab: Modifier Libraries. See Modifier Libraries.
v1.0.6 - Business category picker in the apply wizard
  • The application wizard now includes a required business category picker (Pizzeria, Bakery, Food Truck, ...) alongside the existing cuisine picker, with wizard copy telling the applicant it's admin-only from that point on.
  • Enforced server-side, not just stated: the category field is blocked from the vendor dashboard's autosave and full-form profile save, the same treatment already given to plan and vendor slug. The only way to change it after application is the new dropdown on F! Orders → Vendors.
v1.0.5 - Business category is now operator-editable
  • The global business category moved off a hardcoded list and into Settings → Categories, seeded with 27 defaults on install, fully add/remove/relabel-able without a plugin update.
  • No default or anchor category - every category is a flat peer. "Restaurant & Bar" being the largest category is a go-to-market fact, not something built into the schema.
v1.0.3 - v1.0.4 - Internal rename to vendor/business-generic naming
  • Every class, file, directory, database table, meta key, option key, hook, shortcode tag, and CSS class that hardcoded "restaurant" now uses vendor/business-generic naming internally (FO_Restaurant_ProfileFO_Vendor_Profile, etc.) - architecture only, this didn't change how the platform behaves. Old shortcode tags still work as aliases - see Shortcodes.
  • Vendor application page moved from /restaurant-apply/ to /vendor-apply/; existing installs migrate the page in place rather than creating a duplicate.
v1.0.2 - One page per business, /businesses directory, cuisine field
  • Public profile consolidated to one page per restaurant, not one per location - a restaurant with multiple addresses now has a single page with an in-page location picker. See Public Restaurant Menu.
  • Public URL prefix changed from /restaurant/{slug}/ to /business/{slug}/. This is a rewrite-rule change with no automatic redirect from the old path - see the callout under Public Restaurant Menu.
  • Added the cuisine field (Google Business Profile-style list - Mexican, Peruvian, Salvadoran, ...), self-reported at application and editable from the vendor's own dashboard settings. Distinct from the business category added later in v1.0.5 - see Settings → Categories.
  • The /businesses directory gained a cuisine filter, badges and catering-availability surfaced on each card, and best-effort distance-from-visitor sorting (browser geolocation, never required).
  • Commission hardline: a vendor can no longer complete checkout with no resolvable commission rate. See the callout under Commission Channels.
  • Delivery cancellation wired up to provider APIs on order cancel/refund.
  • Catering availability toggle added to vendor dashboard settings.
  • Admin UI to assign platform badges (New, Top Rated, Verified) per vendor - see Admin Page: Vendors.
v1.0.1 - Menu categories, catering, refund policy, browse page, Google Sign-In
  • Menu categories are now a fixed platform-wide set (Appetizers, Entrees, Dessert, Add-ons, Drinks, Specials, Combos) - restaurants assign items to them but can't create new ones. Added curated dietary-tag checkboxes (Vegetarian, Vegan, Gluten-Free, Dairy-Free, Nut-Free, Halal) and a per-item allergy note.
  • "Available for Commissions" rebuilt as Catering: a restaurant turns on catering availability and sets an optional price-per-head; guests see a live estimate on a short quote form (head count, date/time, address, budget, notes) - not a priced order, a lead for the restaurant to follow up on.
  • Shipping/Returns replaced with a single Refund Policy: a platform-wide default is always shown in full; a restaurant's own notes can only add to it, never override it.
  • "Sold" renamed to "86'd" everywhere a menu item is unavailable, including the shop archive badge.
  • New /browse page - every restaurant's menu items in one cross-restaurant grid, filterable by category, dietary tag, and restaurant. See Shortcodes.
  • Google Sign-In for restaurant applicants (Settings → API Config → Google Sign-In): verifies identity and prefills name/email, skipping the email-confirmation step. Reading an applicant's actual Google Business Profile data is a separate, Google-gated API and not wired up yet.
  • Schema.org markup on the public profile fixed from Person to LocalBusiness.
  • Removed the last non-functional trace of a Follow feature (a guest-facing Follow button/modal that called a class which didn't exist).
  • Demo/seed data rewritten to real menu items, reviews, and messages - previously still art-marketplace content.
  • Full pass removing leftover art-marketplace naming and history from code comments, this documentation, and readme.txt.
2026-07-19 (fifth pass) - Location hours, mobile/rotating locations, season-pause
  • Every location now has a weekly hours schedule (open/close per day, or marked closed), and can be marked "mobile / rotating" so any day of the week carries its own address and label instead of the location's main address - a food truck's known rotation between spots, set by the restaurant. See Locations.
  • Added a season status (Active / Paused, with an optional guest-facing note) for a fixed location that's fully set up but not currently taking orders - e.g. "Closed until March" - independent of daily hours.
  • The restaurant's public page and dashboard Locations list both now show a "Today" status pulling from all of the above - today's mobile stop, closed-today, or paused, resolved by one shared function so the two views can never disagree.
  • Whatever address is live for a location gets frozen onto an order the moment it's placed (_fo_pickup_address_snapshot) - same "lock it at order time" principle as the commission rate - so a restaurant can update a mobile location's spot anytime without it reaching back into an order already placed.
  • Deliberately not built: live GPS tracking or a same-day override pin. This is a recurring schedule the restaurant edits themselves, not a live position feed.
2026-07-19 (fourth pass) - Removed remaining plan/commission redundancy
  • Removed the product/menu-size limit field from the plan schema entirely (not just defaulted to unlimited) - there's no Product Limit column on Settings → Plans anymore, and a plan's stored data can no longer reintroduce a cap even if one is hand-edited in.
  • Settings → Commission's price-based Bands editor was duplicating the per-plan Commission Rate on Settings → Plans. Consolidated to one primary control (Plans); Commission now leads with the holding period and tucks the bands editor into a collapsed "Advanced: price-based fallback" section, only relevant for a tier that leaves its rate blank.
  • Fixed the Setup Wizard's own Vendor Plans step, which had drifted out of sync with the real plan schema (showed a "Standard" plan with price/billing fields that don't exist anymore) - it now matches Settings → Plans exactly and defaults to the real Free/$300/mo/$3,000/yr tiers.
2026-07-19 (third pass) - Multi-location restaurants and a new plan model
  • Restaurants can now register more than one physical location under one login/plan/Stripe account. Each location gets its own address, its own public page, and its own menu. See Locations.
  • Replaced the product-limit-based plan tiers entirely. Plans no longer gate on features or product count - the two real levers are location count and marketplace commission rate. New default tiers: Free (1 location, 10%), $300/mo (unlimited locations, 5%), $3,000/yr (unlimited locations, 3%). See Settings → Plans.
  • The marketplace commission rate now comes from the restaurant's plan first, falling back to the price-based bands only for a plan that leaves its rate blank. See Commission Channels.
2026-07-19 (second pass) - Removed remaining art-marketplace surfaces
  • Removed the Settings → Collector tab and its Certificate of Authenticity toggle, plus the live "Certificate of Authenticity included" block it rendered on menu-item product pages - no restaurant-ordering equivalent.
  • Removed Custom Work Terms everywhere it appeared: the platform default in Settings → Vendors, each restaurant's own override in their dashboard Store Policies section, and the product-page Store Policies tab.
  • Removed the em dash character from all browser-facing text (settings screens, dashboard, public menu page, emails, admin notices); kept where it was already only in code comments.
  • Changed the default commission bands from price-scaling 10/15/20% tiers to a flat, GrubHub/UberEats-style rate: 0% under $10, 5% at $10+. See Settings → Commission. Existing sites keep whatever bands they already have; this only affects new activations.
  • Changed every default plan tier's product limit from a capped number (20/50/100/500) to unlimited (-1). See Settings → Plans. The product-limit field and its enforcement are untouched for operators who want it back - just not the default.
2026-07-19 - Public menu page, docs, and Gallery/Moniker cleanup
  • Every restaurant's public profile now shows a real menu, grouped by native product category, with modifier-aware Add to Cart and a "Choose Options" picker for items with modifier groups.
  • Retired the old no-reload tab-switching AJAX controller - there is exactly one menu per restaurant, nothing left to switch between.
  • ROADMAP.md rewritten to describe F! Orders' actual architecture.
  • This documentation page created, and every settings-tab "Guide →" link (previously pointing at immibrand.com/docs) repointed here.
  • Removed the Gallery, Moniker, and Moniker Brands Directory auto-created pages and their shortcodes - they should never have been created on a restaurant-ordering site. See Legacy Surfaces & Known Issues.

Next up

  • Restaurant review categories (Service/Ambiance/Food & Drinks breakdown, beyond today's single overall rating) Coming Soon
  • DoorDash Drive checkout-time fee (matching what Uber Direct already has) Coming Soon
  • Real cross-browser verification of the white-label checkout's Storage Access API flow before high-volume use
  • Grubhub Direct authentication and dispatch