Skip to content

Hendrickx Onboarding (Reseller) ​

Overview ​

How a reseller (partner) brings a merchant onto Upvendo with the Hendrickx in-house POS. Hendrickx is part of the Kassanet family (shared with Vanhoutte) and is a location-specific integration: each location connects its own Hendrickx register with its own credentials.

Two distinct surfaces are involved:

  1. The partner "Clients" portal — where a reseller creates the merchant account and sends login credentials.
  2. The merchant back-office — where the Hendrickx POS is actually connected and the menu synced (route /hendrickx, handled by KassanetIntegrationController). There is no reseller-specific Hendrickx connect screen; the connection is made in the merchant's own back-office (which a partner can enter on the merchant's behalf).

The end-to-end mechanics of the integration itself (encryption, order push, table allocation, logs, etc.) are documented in Hendrickx Integration and Kassanet Integration. This page covers only the onboarding/reseller-facing flow.


Reseller Surface (How a Partner Manages Merchants) ​

The reseller system in the back-office splits roles into a global level and a reseller level (App\Constants\Roles):

  • Global Administrator — creates and manages reseller companies, assigns/unassigns merchants to a reseller, and manages reseller users. All /back-office/admin/resellers/* routes are gated to global-administrator only (checked in ResellerController).
  • Reseller Owner / Reseller Administrator (the "partner") — can create new merchants for their own reseller and send them credentials, and manage their clients' billing/subscriptions.

What a partner can do (verified) ​

ActionWhere (back-office route)APIAuthorization
Create a merchant ("client")Clients portal (src/pages/clients/, ClientFormDialog.vue)POST /back-office/partner/merchants (ResellerController::createMerchant)isGlobalUser — i.e. global admin or reseller-owner/reseller-administrator (CreateMerchantRequest::authorize)
Send login credentials to a merchantClient detail pagePOST /back-office/partner/merchants/{merchantId}/send-credentials (sendMerchantCredentials)Global user; a reseller is restricted to merchants whose reseller_id matches their own
View / manage a client's subscriptions & invoicesClient detail page/back-office/reseller/{resellerId}/billing/* (ResellerBillingController)Reseller billing routes
See which POS providers their reseller supportsUsed by Emily onboardingGET /back-office/merchant/reseller-providers (getMerchantResellerProviders)Current merchant's reseller

When a partner creates a merchant, the backend (ResellerService::createMerchant):

  • Creates the merchant + owner user (first_name, last_name, email, password, optional business_name/phone, required locale).
  • Sets reseller_id to the partner's reseller, onboarding_type = reseller, merchant_status = New, and stripe_onboarding_completed = true.
  • Flags the new user must_change_password = true (the merchant changes it on first login).
  • If the creator is a reseller, adds the new merchant to the partner user's vendor_ids so the partner can act on its behalf.

A reseller can act on behalf of one of its merchants (merchant switching exists via POST /back-office/set-merchant), and PermissionService::hasPermission explicitly allows a reseller to operate on a merchant it has access to. The connect-Hendrickx steps below are then performed inside that merchant's back-office.

What a partner cannot do ​

  • Creating, updating, or deleting reseller companies, assigning/unassigning merchants to a reseller, listing all merchants, and managing reseller users are Global Administrator only.
  • There is no separate "reseller onboarding wizard" for the POS connection — the merchant (or the partner on its behalf) uses the standard back-office flow and Emily Guided Setup.

Connecting Hendrickx (in the merchant back-office) ​

The actual POS connection happens at route /hendrickx in the merchant back-office (frontend src/pages/hendrickx/, dialog HendrickxChannelDialog.vue). The backend route group is POST/GET /api/back-office/kassanet/{provider}/{locationId} with {provider} = hendrickx, served by KassanetIntegrationController.

Prerequisites (verified) ​

  • Branding profile — the only profile enforced at connect time. The business profile (payments and Upvendo billing in one) is not checked at connect and can be set up later, but it is required before taking customer payments.
  • A location must exist and be selected in the topbar before connecting (the connection is location-scoped).
  • The menu must already exist in Hendrickx. Menu sync is one-way (Hendrickx → Upvendo); Upvendo cannot create items in the POS.
  • No other in-house POS on the location/merchant. A location may have only one in-house POS. Hendrickx, Vanhoutte, and ShopCaisse are mutually exclusive per location and also conflict with the merchant-scoped in-house POS (Square, MplusKassa, Lightspeed). A conflict aborts with HTTP 400 "Another in-house channel is already enabled for this merchant" (ThirdPartyIntegrationHelper::checkForInhouseChannel).

Credentials (3 fields + optional sync time, verified) ​

The connect form (HendrickxForm.vue) and InitiateKassanetRequest accept exactly:

  • Connection URL (base_url) — required, validated as a URL. The Hendrickx OrderConnector / Kassanet API base URL.
  • Signature Key (signature_key) — required string. HMAC-SHA256 signing key.
  • Encryption Token (encryption_token) — required string. Blowfish encryption key.
  • Daily Sync Time (sync_time) — optional. Must be HH:00 or HH:30 (minutes restricted to 00/30). The value is the local time at the location, stored as that local wall-clock time, so it stays put across daylight-saving changes. The form shows the resolved timezone as a caption under the field ("Runs in the ... timezone"); the merchant does not pick it. Clear it to disable auto-sync.

Saving the form connects the integration and auto-triggers a menu sync. (Encryption is controlled by an enable_encryption flag that defaults to on, not a field on this form.)


Onboarding Steps (Emily Guided Setup) ​

Once the partner is in the merchant's context (client mode), or the merchant logs in, onboarding is driven by Emily Guided Setup (see Guided Setup). Emily asks the discovery questions below, then hands back the checklist for the channel that was picked. Hendrickx is in the production PRODUCTION_POS_PROVIDERS list (with Upvendo POS and Vanhoutte — though Upvendo POS is staged test_only server-side, config/pos-providers.php:51-52), and in client mode the provider choice is filtered to the reseller's own pos_providers.

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.

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 Hendrickx?",
    "text_i18n": {
      "nl": "Heeft de handelaar het menu ingesteld in Hendrickx?",
      "fr": "Le marchand a-t-il configuré son menu dans Hendrickx ?",
      "de": "Hat der Händler sein Menü in Hendrickx eingerichtet?",
      "es": "¿El comercio tiene su menú configurado en Hendrickx?",
      "it": "Il commerciante ha configurato il proprio menu in Hendrickx?",
      "pt": "O comerciante tem o menu configurado no Hendrickx?"
    },
    "options": [
      "Yes, menu is in Hendrickx",
      "No, not yet"
    ],
    "labels": {
      "nl": {
        "Yes, menu is in Hendrickx": "Ja, menu staat in Hendrickx",
        "No, not yet": "Nee, nog niet"
      },
      "fr": {
        "Yes, menu is in Hendrickx": "Oui, le menu est dans Hendrickx",
        "No, not yet": "Non, pas encore"
      },
      "de": {
        "Yes, menu is in Hendrickx": "Ja, das Menü ist in Hendrickx",
        "No, not yet": "Nein, noch nicht"
      },
      "es": {
        "Yes, menu is in Hendrickx": "Sí, el menú está en Hendrickx",
        "No, not yet": "No, todavía no"
      },
      "it": {
        "Yes, menu is in Hendrickx": "Sì, il menu è in Hendrickx",
        "No, not yet": "No, non ancora"
      },
      "pt": {
        "Yes, menu is in Hendrickx": "Sim, o menu está no Hendrickx",
        "No, not yet": "Não, ainda não"
      }
    },
    "abort_if": "No, not yet",
    "abort_message": "The merchant needs their menu set up in Hendrickx first. Hendrickx only supports pulling menu items from the POS - items cannot be created in Upvendo. Please have them create their menu items in Hendrickx, then restart the onboarding.",
    "abort_message_i18n": {
      "nl": "De handelaar moet het menu eerst in Hendrickx instellen. Hendrickx ondersteunt alleen het ophalen van menu-items uit de kassa — items kunnen niet in Upvendo worden aangemaakt. Laat hen hun menu-items in Hendrickx aanmaken en start daarna de onboarding opnieuw.",
      "fr": "Le marchand doit d'abord configurer son menu dans Hendrickx. Hendrickx 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 Hendrickx, puis recommencez l'intégration.",
      "de": "Der Händler muss sein Menü zuerst in Hendrickx einrichten. Hendrickx 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 Hendrickx anlegen und starten Sie dann das Onboarding neu.",
      "es": "El comercio debe configurar primero su menú en Hendrickx. Hendrickx 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 Hendrickx y luego reinicia el onboarding.",
      "it": "Il commerciante deve prima configurare il proprio menu in Hendrickx. Hendrickx supporta solo il recupero degli articoli dal POS — gli articoli non possono essere creati in Upvendo. Chiedigli di creare i suoi articoli in Hendrickx e poi riavvia l'onboarding.",
      "pt": "O comerciante precisa de configurar primeiro o menu no Hendrickx. O Hendrickx 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 Hendrickx 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 Hendrickx."},
    {"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
    {"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Hendrickx is location-specific: each location connects separately with its own credentials."},
    {"id": "connect_pos", "title": "Connect Hendrickx", "route": "/hendrickx", "channel": "hendrickx", "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 'hendrickx'."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/hendrickx", "note": "One-way import from Hendrickx (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 Hendrickx, 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 Hendrickx."},
    {"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
    {"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Hendrickx is location-specific: each location connects separately with its own credentials."},
    {"id": "connect_pos", "title": "Connect Hendrickx", "route": "/hendrickx", "channel": "hendrickx", "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 'hendrickx'."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/hendrickx", "note": "One-way import from Hendrickx (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 Hendrickx, 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 Hendrickx."},
    {"id": "create_location", "title": "Create Location", "route": "/settings/locations"},
    {"id": "select_location", "title": "Select Location from Topbar", "action": "open_location_selector", "note": "Hendrickx is location-specific: each location connects separately with its own credentials."},
    {"id": "connect_pos", "title": "Connect Hendrickx", "route": "/hendrickx", "channel": "hendrickx", "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 'hendrickx'."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/hendrickx", "note": "One-way import from Hendrickx (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 Hendrickx, 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}
  ]
}

Notes on the steps:

  • The channel checklists mirror the merchant Hendrickx 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.
  • Of the profile steps, only Store Branding is enforced at connect time. The "Set up Business Profile" step carries optional_for_go_live; it is not needed to connect Hendrickx, but it is required before taking customer payments. (It replaces the former "Set up Payment Profile" + "Set up Billing Profile" pair: billing is the business profile's Billing tab.)
  • The "Select Location from Topbar" step is an action (open_location_selector), not a navigation route.
  • "Connect Hendrickx" and "Sync Menu" both point at /hendrickx.
  • Completion is auto-detected from the merchant's real data via GET /back-office/setup-status — the merchant does not tick steps off manually. "Connect Hendrickx" is only marked complete when the integration is both connected and has a successful test.
  • Kiosk device setup lives under /device-management/profiles and /device-management/devices; online ordering is configured at /online/online-ordering with settings at /online-settings.

Payments ​

After menu sync, orders are pushed to Hendrickx only after the customer pays. Upvendo collects the payment and Kassanet records the tender on the POS bill:

  • Kiosk — Viva Wallet (terminal).
  • Online Ordering / Table QR — Stripe.

Setting up a business profile (Settings → Business profile) is required to accept customer payments, but is not required to connect Hendrickx. (Payment specifics: see Hendrickx Integration.)


Discovery Notes ​

The two discovery questions above are what Emily asks a partner in client mode. Both rest on code-true constraints:

  • Menu must exist in Hendrickx first — the menu sync is one-way (Hendrickx → Upvendo only; see Hendrickx Integration and Kassanet Integration). If the merchant has no menu in Hendrickx there is nothing to import, so the "No, not yet" answer stops the flow and asks the partner to have the menu built in the POS first.
  • Channel choice (Kiosk / Online ordering / Both) selects which checklist the proxy returns.

Not verified here: how the back-office widget renders the questions and checklist (that lives in upvendo-backoffice, not in this document).


FAQs ​

Q: How does a reseller add a new merchant? A: From the partner Clients portal, using the "create client" form (name, email, password, optional business name/phone, required locale). This calls POST /back-office/partner/merchants. The merchant is created with onboarding_type = reseller and must_change_password = true, and is linked to the reseller via reseller_id. The partner can then send login credentials.

Q: Can a reseller connect Hendrickx for the merchant? A: The connection is made in the merchant's back-office at /hendrickx. A partner with access to the merchant can act on its behalf (merchant switching) and perform the same flow; there is no separate reseller-only connect screen.

Q: What credentials are needed to connect Hendrickx? A: Three: Connection URL (base_url), Signature Key (signature_key), and Encryption Token (encryption_token), plus an optional daily Sync Time (HH:00/HH:30, read as the location's local time).

Q: Which profiles must be set up before connecting? A: Only a branding profile is enforced. The business profile (which includes billing) can be added later, but it is required before taking customer payments.

Q: Can a location run Hendrickx plus another POS? A: No. One in-house POS per location. Hendrickx, Vanhoutte, and ShopCaisse are mutually exclusive per location and conflict with merchant-scoped Square/MplusKassa/Lightspeed.

Q: Who can create reseller companies and assign merchants to them? A: Global Administrators only. Reseller owners/administrators can create merchants for their own reseller and manage their clients' billing, but not other resellers or the reseller-company records themselves.