Appearance
Vanhoutte Onboarding Flow (Reseller)
Overview
This is the onboarding flow for a reseller (Partner Admin) setting up one of their merchants on Vanhoutte POS. Vanhoutte is a Kassanet-family in-house POS, the twin of Hendrickx; the two share the same backend engine and differ only by provider identifier. The connection lives at route /vanhoutte.
Vanhoutte is location-specific: each location connects separately with its own credentials. Menu data is pulled one-way from the POS (Vanhoutte to Upvendo); items cannot be created in Upvendo. Orders are forwarded to the POS after payment is confirmed (Viva Wallet for Kiosk, Stripe for Online Ordering).
For the full integration mechanics (sync jobs, order flow, table allocation, encryption, troubleshooting), see the shared Vanhoutte Integration and Kassanet Integration docs. This page covers only the reseller-specific path to that integration.
Who can do this
The reseller surface is gated to reseller roles (app/Constants/Roles.php):
reseller-owner(RESELLER_OWNER) — full access; can create and onboard merchantsreseller-administrator(RESELLER_ADMINISTRATOR) — full access; can create and onboard merchantsreseller-rep(RESELLER_REP) — read-only, limited to viewing their own assigned merchants, commissions, and settlements. A reseller-rep cannot open the Clients page or create/onboard merchants (the/clientsroute requiresreseller-analytics, and the create-merchant endpoint is gated to global/owner/administrator viaPermissionService::isGlobalUser).
A global-administrator can also onboard a merchant the same way as reseller-owner/administrator. Reseller-owner, reseller-administrator, and global-admin work happens through client mode (see below), which puts the user into the merchant's context so the normal merchant onboarding applies.
Step 1 — Create the merchant (Client)
Reseller-owners and reseller-administrators (and global-admins via client mode) create and manage merchants from the Clients page (/clients); reseller-reps do not have access to this page. Click to add a client, which opens the create-client dialog (ClientFormDialog.vue). The form collects:
- First name (required)
- Last name (required)
- Email (required)
- Password (required)
- Phone (optional)
- Business name (optional — defaults to "{First name}'s Account" if left blank)
- Country / locale (defaults to Belgium)
- Default language (defaults to English)
Submitting calls POST /api/back-office/partner/merchants (ResellerController::createMerchant → ResellerService::createMerchant). This creates the merchant with onboarding_type = reseller, links it to the reseller (reseller_id), forces a password change on first login (must_change_password = true), and initializes the merchant's tables.
After creation, the reseller can open the client's detail page (/clients/{id}) and use Send Credentials (POST /api/back-office/partner/merchants/{merchantId}/send-credentials) to email the merchant owner a temporary password.
Not verified here: the exact email content/delivery and any reseller-side process steps outside these repos.
Step 2 — Enter client mode
To configure the merchant's POS, branding, location, etc., the reseller switches into client mode (merchantMode, persisted in localStorage). In client mode the reseller/admin operates inside the merchant's context: Emily and Guided Setup use the merchant onboarding endpoints, and the in-house channel grid is filtered to the reseller's assigned POS providers (GET /api/back-office/merchant/reseller-providers, ResellerService::getMerchantResellerProviders).
If a merchant has a reseller, only that reseller's allowed providers are shown; otherwise the provider list falls back to the country/environment-filtered catalog from config/pos-providers.php.
Step 3 — Onboard the merchant (standard Guided Setup)
From client mode, the reseller follows the same Guided Setup / Emily onboarding a self-serve merchant would; there is no separate reseller-specific wizard in the back-office code. Emily asks the discovery questions below and then returns the checklist for the channel that was picked (see Guided Setup). The checklists mirror the merchant Vanhoutte onboarding and use the canonical step ids the back office derives from /api/back-office/setup-status (src/utils/emily/setupStepIds.ts); the legacy BASE_SETUP_STEPS constants in components/dialogs/guided-setup/types.ts are not what a merchant sees.
Completion is auto-detected by the backend (GET /api/back-office/setup-status) from the merchant's real data, not marked manually.
Note: Although the checklist lists "Set up Business Profile", the only profile actually enforced at connect time for Vanhoutte is Branding (see "Connecting Vanhoutte" below). The business profile (payments and Upvendo billing in one) is not required to connect the POS and carries
optional_for_go_live, but it is required before accepting customer payments.
Discovery Flow (JSON - for deterministic parsing)
The proxy (parseOnboardingDocument in upvendo-backend-proxy src/emily/merchant/onboarding-state.js) asks these questions in order and then returns setup_steps[<channels answer>] — so the setup_steps keys must be exactly the channels options (Kiosk only, Online ordering only, Both). _reseller_prerequisites is an underscore meta-group the proxy never indexes; it documents the partner-side steps that come before client mode.
The menu question rests on the one-way menu sync (Vanhoutte → Upvendo only; see Vanhoutte Integration): with no menu in Vanhoutte there is nothing to import, so "No, not yet" stops the flow.
discovery_questions
[
{
"id": "channels",
"text": "Which channels for this merchant?",
"text_i18n": {
"nl": "Welke kanalen voor deze handelaar?",
"fr": "Quels canaux pour ce marchand ?",
"de": "Welche Kanäle für diesen Händler?",
"es": "¿Qué canales para este comercio?",
"it": "Quali canali per questo commerciante?",
"pt": "Que canais para este comerciante?"
},
"options": [
"Kiosk only",
"Online ordering only",
"Both"
],
"labels": {
"nl": {
"Kiosk only": "Alleen kiosk",
"Online ordering only": "Alleen online bestellen",
"Both": "Beide"
},
"fr": {
"Kiosk only": "Kiosque uniquement",
"Online ordering only": "Commande en ligne uniquement",
"Both": "Les deux"
},
"de": {
"Kiosk only": "Nur Kiosk",
"Online ordering only": "Nur Online-Bestellung",
"Both": "Beides"
},
"es": {
"Kiosk only": "Solo quiosco",
"Online ordering only": "Solo pedidos en línea",
"Both": "Ambos"
},
"it": {
"Kiosk only": "Solo chiosco",
"Online ordering only": "Solo ordini online",
"Both": "Entrambi"
},
"pt": {
"Kiosk only": "Apenas quiosque",
"Online ordering only": "Apenas pedidos online",
"Both": "Ambos"
}
}
},
{
"id": "menu_ready",
"text": "Does the merchant have their menu set up in Vanhoutte?",
"text_i18n": {
"nl": "Heeft de handelaar het menu ingesteld in Vanhoutte?",
"fr": "Le marchand a-t-il configuré son menu dans Vanhoutte ?",
"de": "Hat der Händler sein Menü in Vanhoutte eingerichtet?",
"es": "¿El comercio tiene su menú configurado en Vanhoutte?",
"it": "Il commerciante ha configurato il proprio menu in Vanhoutte?",
"pt": "O comerciante tem o menu configurado no Vanhoutte?"
},
"options": [
"Yes, menu is in Vanhoutte",
"No, not yet"
],
"labels": {
"nl": {
"Yes, menu is in Vanhoutte": "Ja, menu staat in Vanhoutte",
"No, not yet": "Nee, nog niet"
},
"fr": {
"Yes, menu is in Vanhoutte": "Oui, le menu est dans Vanhoutte",
"No, not yet": "Non, pas encore"
},
"de": {
"Yes, menu is in Vanhoutte": "Ja, das Menü ist in Vanhoutte",
"No, not yet": "Nein, noch nicht"
},
"es": {
"Yes, menu is in Vanhoutte": "Sí, el menú está en Vanhoutte",
"No, not yet": "No, todavía no"
},
"it": {
"Yes, menu is in Vanhoutte": "Sì, il menu è in Vanhoutte",
"No, not yet": "No, non ancora"
},
"pt": {
"Yes, menu is in Vanhoutte": "Sim, o menu está no Vanhoutte",
"No, not yet": "Não, ainda não"
}
},
"abort_if": "No, not yet",
"abort_message": "The merchant needs their menu set up in Vanhoutte first. Vanhoutte only supports pulling menu items from the POS - items cannot be created in Upvendo. Please have them create their menu items in Vanhoutte, then restart the onboarding.",
"abort_message_i18n": {
"nl": "De handelaar moet het menu eerst in Vanhoutte instellen. Vanhoutte ondersteunt alleen het ophalen van menu-items uit de kassa — items kunnen niet in Upvendo worden aangemaakt. Laat hen hun menu-items in Vanhoutte aanmaken en start daarna de onboarding opnieuw.",
"fr": "Le marchand doit d'abord configurer son menu dans Vanhoutte. Vanhoutte ne prend en charge que la récupération des articles depuis la caisse — les articles ne peuvent pas être créés dans Upvendo. Demandez-lui de créer ses articles dans Vanhoutte, puis recommencez l'intégration.",
"de": "Der Händler muss sein Menü zuerst in Vanhoutte einrichten. Vanhoutte unterstützt nur das Abrufen von Artikeln aus dem Kassensystem — Artikel können nicht in Upvendo erstellt werden. Lassen Sie ihn seine Menüartikel in Vanhoutte anlegen und starten Sie dann das Onboarding neu.",
"es": "El comercio debe configurar primero su menú en Vanhoutte. Vanhoutte solo admite obtener artículos desde el TPV — los artículos no se pueden crear en Upvendo. Pídele que cree sus artículos en Vanhoutte y luego reinicia el onboarding.",
"it": "Il commerciante deve prima configurare il proprio menu in Vanhoutte. Vanhoutte supporta solo il recupero degli articoli dal POS — gli articoli non possono essere creati in Upvendo. Chiedigli di creare i suoi articoli in Vanhoutte e poi riavvia l'onboarding.",
"pt": "O comerciante precisa de configurar primeiro o menu no Vanhoutte. O Vanhoutte só permite obter artigos a partir do POS — os artigos não podem ser criados no Upvendo. Peça-lhe para criar os seus artigos no Vanhoutte e depois reinicie o onboarding."
}
}
]setup_steps
{
"_reseller_prerequisites": [
{"id": "create_client", "title": "Create the Client Merchant", "route": "/clients", "modal_type": "ClientFormDialog", "note": "Partner Admin only. POSTs to /back-office/partner/merchants. No POS provider is chosen here; it is selected later in the client's context."},
{"id": "send_credentials", "title": "Send Login Credentials", "route": "/clients/:id", "note": "Sends a temp password to the merchant owner (POST /back-office/partner/merchants/{merchantId}/send-credentials). Then operate in the client's context (client mode) to continue."}
],
"Kiosk only": [
{"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "note": "The only profile enforced when connecting Vanhoutte."},
{"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
{"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Vanhoutte is location-specific: each location connects separately with its own credentials."},
{"id": "connect_pos", "title": "Connect Vanhoutte", "route": "/vanhoutte", "channel": "vanhoutte", "note": "Enter the location's three Kassanet credentials: Connection URL (base_url), Signature Key (signature_key), Encryption Token (encryption_token). Available only if the reseller's pos_providers includes 'vanhoutte'."},
{"id": "sync_menu", "title": "Sync Menu", "route": "/vanhoutte", "note": "One-way import from Vanhoutte (menu items must already exist in the POS)."},
{"id": "menu_for_kiosk", "title": "Build Menu for Kiosk", "route": "/menus/menu-builder"},
{"id": "in_house_settings", "title": "Set up In-House Settings", "route": "/in-house/settings"},
{"id": "device_profile", "title": "Create Device Profile", "route": "/device-management/profiles"},
{"id": "billing_details", "title": "Add Billing Details", "route": "/settings/payments?tab=billing"},
{"id": "kiosk_device", "title": "Set up Kiosk & Activate Payment", "route": "/device-management/devices"},
{"id": "business_profile", "title": "Set up Business Profile", "route": "/settings/payments", "optional_for_go_live": true,"note": "Not required to connect Vanhoutte, but needed to accept customer payments (Stripe)."},
{"id": "test_kiosk", "title": "Activate Kiosk & Test Order", "route": "/device-management/devices", "optional_for_go_live": true}
],
"Online ordering only": [
{"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "note": "The only profile enforced when connecting Vanhoutte."},
{"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
{"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Vanhoutte is location-specific: each location connects separately with its own credentials."},
{"id": "connect_pos", "title": "Connect Vanhoutte", "route": "/vanhoutte", "channel": "vanhoutte", "note": "Enter the location's three Kassanet credentials: Connection URL (base_url), Signature Key (signature_key), Encryption Token (encryption_token). Available only if the reseller's pos_providers includes 'vanhoutte'."},
{"id": "sync_menu", "title": "Sync Menu", "route": "/vanhoutte", "note": "One-way import from Vanhoutte (menu items must already exist in the POS)."},
{"id": "menu_for_online_ordering", "title": "Build Menu for Online Ordering", "route": "/menus/menu-builder"},
{"id": "online_settings", "title": "Set up Online Settings", "route": "/online-settings"},
{"id": "configure_online_ordering", "title": "Set up Online Ordering", "route": "/online/online-ordering"},
{"id": "business_profile", "title": "Set up Business Profile", "route": "/settings/payments", "optional_for_go_live": true,"note": "Not required to connect Vanhoutte, but needed to accept customer payments (Stripe)."},
{"id": "test_online_ordering", "title": "Open Online Ordering URL & Test Order", "route": "/online/online-ordering", "optional_for_go_live": true}
],
"Both": [
{"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "note": "The only profile enforced when connecting Vanhoutte."},
{"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
{"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Vanhoutte is location-specific: each location connects separately with its own credentials."},
{"id": "connect_pos", "title": "Connect Vanhoutte", "route": "/vanhoutte", "channel": "vanhoutte", "note": "Enter the location's three Kassanet credentials: Connection URL (base_url), Signature Key (signature_key), Encryption Token (encryption_token). Available only if the reseller's pos_providers includes 'vanhoutte'."},
{"id": "sync_menu", "title": "Sync Menu", "route": "/vanhoutte", "note": "One-way import from Vanhoutte (menu items must already exist in the POS)."},
{"id": "menu_for_kiosk", "title": "Build Menu for Kiosk", "route": "/menus/menu-builder"},
{"id": "menu_for_online_ordering", "title": "Build Menu for Online Ordering", "route": "/menus/menu-builder"},
{"id": "in_house_settings", "title": "Set up In-House Settings", "route": "/in-house/settings"},
{"id": "device_profile", "title": "Create Device Profile", "route": "/device-management/profiles"},
{"id": "billing_details", "title": "Add Billing Details", "route": "/settings/payments?tab=billing"},
{"id": "kiosk_device", "title": "Set up Kiosk & Activate Payment", "route": "/device-management/devices"},
{"id": "online_settings", "title": "Set up Online Settings", "route": "/online-settings"},
{"id": "configure_online_ordering", "title": "Set up Online Ordering", "route": "/online/online-ordering"},
{"id": "business_profile", "title": "Set up Business Profile", "route": "/settings/payments", "optional_for_go_live": true,"note": "Not required to connect Vanhoutte, but needed to accept customer payments (Stripe)."},
{"id": "test_kiosk", "title": "Activate Kiosk & Test Order", "route": "/device-management/devices", "optional_for_go_live": true},
{"id": "test_online_ordering", "title": "Open Online Ordering URL & Test Order", "route": "/online/online-ordering", "optional_for_go_live": true}
]
}Connecting Vanhoutte
Route: /vanhoutte
- Select the target location from the topbar.
- Open the Vanhoutte connect dialog (
VanHoutteChannelDialog). - Enter the three credentials (provided by Vanhoutte):
- Connection URL (
base_url) - Signature Key (
signature_key) - Encryption Token (
encryption_token)
- Connection URL (
- Connect, then click Sync Now to import the menu.
Connecting calls POST /api/back-office/kassanet/vanhoutte/{locationId} (the initiate action). The connect dialog collects only those three credentials — there is no sync-time field in the dialog.
Prerequisites enforced at connect time (ThirdPartyIntegrationHelper::checkIntegrationPrerequisites):
- Branding profile must exist, or the connect is rejected ("Missing required profiles: branding profile…"). The business profile (including billing) is not checked at connect, but is required before accepting customer payments.
- In-house exclusivity (
checkForInhouseChannel): a location may have only one in-house POS. Hendrickx, Vanhoutte, and ShopCaisse are mutually exclusive per location (IN_HOUSE_CHANNELS), and these also conflict with the merchant-scoped in-house providers Square, MplusKassa, and Lightspeed (IN_HOUSE_MERCHANT_SCOPED_CHANNELS). A conflict is rejected with "Another in-house channel is already enabled for this merchant."
Disconnecting a Vanhoutte integration through the app is currently disabled. To remove or change a connection, contact an administrator.
Auto Sync (sync_time)
The daily auto-sync time is not part of the connect dialog. It is set after connecting, in the Vanhoutte settings form (VanHoutteForm.vue):
- The time picker is time-only (
H:i, 24h) and is snapped to the nearest hour or half-hour (:00or:30). - The time you enter is the location's local time and is stored as that local wall-clock (
settings.sync_time) — not converted to UTC — alongside the resolved zone insettings.sync_timezone. It therefore stays at the same local time across daylight-saving changes. The form shows the resolved zone as a caption under the picker; the merchant does not choose it. - Auto-sync is driven by the
in-house:auto-synccommand, which runs every 30 minutes and triggers a full menu sync for each integration when its scheduled time arrives, so each integration syncs at most once per day. - Clear the field to disable auto-sync.
Reseller Notes
- A reseller can only offer the POS providers assigned to their reseller account; the in-house channel grid is filtered accordingly in client mode.
- Menu items are pulled from Vanhoutte (one-way). The merchant's menu must exist in Vanhoutte before syncing — it cannot be created in Upvendo.
- Vanhoutte requires Branding only to connect; The business profile is not checked at connect (but it is still part of the Guided Setup checklist and needed for live ordering — Viva Wallet for Kiosk, Stripe for Online Ordering).
- Created merchants start with
must_change_password = true; the merchant owner sets their own password on first login after receiving credentials.
Not verified here: reseller portal navigation/UX beyond the routes named above, POS-side device/menu setup inside Vanhoutte, billing/invoicing process between reseller and merchant, and any sales/contract steps. These live outside the backend and backoffice repositories.
Related Documentation
- Vanhoutte Integration (full mechanics)
- Hendrickx Integration (twin Kassanet POS)
- Kassanet Integration (shared engine)
- Guided Setup (the onboarding wizard used here)