Skip to content

Shopcaisse Onboarding Flow

Overview

Shopcaisse (also marketed as EasyShop) is an in-house POS that Upvendo connects to as a read-only inbound integration: Upvendo pulls the existing product catalog out of Shopcaisse, and after a customer pays in an Upvendo channel (Kiosk, Online Ordering, QR Ordering) Upvendo pushes the order back into Shopcaisse. Shopcaisse stays the source of truth for the catalog and the in-store POS. Shopcaisse does not process payments — the merchant still needs a separate payment provider (e.g. Stripe for Online Ordering; a payment terminal for Kiosk).

Production status: Shopcaisse is marked test_only in Upvendo's POS provider config (config/pos-providers.php). It is enabled for testing/onboarding, not a fully GA self-serve connector. Consult the Upvendo team before using it in production.

Key Concepts

  1. You connect by pasting one token, not by picking a store. The connect form asks only for the Shopcaisse JWT bearer token. Upvendo authenticates it, reads every store the token covers, and auto-creates one Upvendo Location per Shopcaisse store (ShopCaisseIntegrationService::syncLocations). There is no manual store-picker step in the current UI.
  2. Locations are created for you. You do not create Upvendo locations first and then map them to stores. Connecting creates the locations and records a store → location map, then automatically syncs each mapped location's catalog.
  3. One Shopcaisse store maps to exactly one Upvendo location. The store binding is permanent — re-pointing a location at a different store is rejected (it would orphan the synced catalog and orders).
  4. One in-house channel per merchant. Shopcaisse is an in-house channel; a merchant can only have one in-house channel active at a time (see In-House Exclusivity).
  5. The token decides the environment. The JWT's namespace claim selects the Shopcaisse API host (prodapi.shop-caisse.com, stagingapi-staging.shop-caisse.com). There is no manual environment toggle.

Shopcaisse Structure

  • Company → owns the product catalog; items/modifiers/menus are read per company.
  • Store → operational unit; each store maps to one Upvendo location.
  • POS → point-of-sale device within a store (where orders are received).
  • External Application → the Shopcaisse-side configuration that issues the JWT bearer token and defines its store/company scope, permissions, and webhook URL.

Integration Type

  • Scope: In-house, merchant-level connect. One paste of a JWT auto-creates one Upvendo location per Shopcaisse store the token covers.
  • Payment Processing: NOT included. The merchant needs a separate payment provider (Stripe for Online Ordering, a payment terminal for Kiosk).
  • Menu Sync: One-way pull from Shopcaisse (Shopcaisse → Upvendo). The catalog must exist in Shopcaisse first.
  • Order Sync: Push to Shopcaisse (Upvendo → Shopcaisse) after payment is confirmed.
  • Token Model: The merchant creates an External Application in Shopcaisse to generate the JWT bearer token, then pastes it into Upvendo.

Discovery Flow (JSON - for deterministic parsing)

discovery_questions
[
  {
    "id": "channels",
    "text": "What channels do you want to set up with Shopcaisse?",
    "text_i18n": {
      "nl": "Welke kanalen wil je instellen met Shopcaisse?",
      "fr": "Quels canaux souhaitez-vous configurer avec Shopcaisse ?",
      "de": "Welche Kanäle möchten Sie mit Shopcaisse einrichten?",
      "es": "¿Qué canales quieres configurar con Shopcaisse?",
      "it": "Quali canali vuoi configurare con Shopcaisse?",
      "pt": "Que canais quer configurar com o Shopcaisse?"
    },
    "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": "Do you already have your menu set up in Shopcaisse?",
    "text_i18n": {
      "nl": "Heb je je menu al ingesteld in Shopcaisse?",
      "fr": "Votre menu est-il déjà configuré dans Shopcaisse ?",
      "de": "Ist Ihr Menü bereits in Shopcaisse eingerichtet?",
      "es": "¿Ya tienes tu menú configurado en Shopcaisse?",
      "it": "Hai già configurato il tuo menu in Shopcaisse?",
      "pt": "Já tem o seu menu configurado no Shopcaisse?"
    },
    "options": [
      "Yes, my menu is in Shopcaisse",
      "No, I need to create it"
    ],
    "labels": {
      "nl": {
        "Yes, my menu is in Shopcaisse": "Ja, mijn menu staat in Shopcaisse",
        "No, I need to create it": "Nee, ik moet het nog aanmaken"
      },
      "fr": {
        "Yes, my menu is in Shopcaisse": "Oui, mon menu est dans Shopcaisse",
        "No, I need to create it": "Non, je dois le créer"
      },
      "de": {
        "Yes, my menu is in Shopcaisse": "Ja, mein Menü ist in Shopcaisse",
        "No, I need to create it": "Nein, ich muss es erst erstellen"
      },
      "es": {
        "Yes, my menu is in Shopcaisse": "Sí, mi menú está en Shopcaisse",
        "No, I need to create it": "No, necesito crearlo"
      },
      "it": {
        "Yes, my menu is in Shopcaisse": "Sì, il mio menu è in Shopcaisse",
        "No, I need to create it": "No, devo crearlo"
      },
      "pt": {
        "Yes, my menu is in Shopcaisse": "Sim, o meu menu está no Shopcaisse",
        "No, I need to create it": "Não, preciso de o criar"
      }
    },
    "abort_if": "No, I need to create it",
    "abort_message": "Shopcaisse requires your menu to be set up in the POS first. Upvendo cannot create your catalog in Shopcaisse — please create your menu items in Shopcaisse, then come back to continue the setup.",
    "abort_message_i18n": {
      "nl": "Shopcaisse vereist dat je menu eerst in de kassa staat. Upvendo kan je catalogus niet in Shopcaisse aanmaken — maak je menu-items aan in Shopcaisse en kom daarna terug om de installatie te voltooien.",
      "fr": "Shopcaisse nécessite que votre menu soit d'abord configuré dans le système de caisse. Upvendo ne peut pas créer votre catalogue dans Shopcaisse — créez vos articles dans Shopcaisse, puis revenez pour poursuivre la configuration.",
      "de": "Shopcaisse erfordert, dass Ihr Menü zuerst im Kassensystem eingerichtet ist. Upvendo kann Ihren Katalog nicht in Shopcaisse anlegen — legen Sie Ihre Menüartikel in Shopcaisse an und kehren Sie dann zurück, um die Einrichtung fortzusetzen.",
      "es": "Shopcaisse requiere que tu menú esté configurado primero en el TPV. Upvendo no puede crear tu catálogo en Shopcaisse — crea tus artículos en Shopcaisse y luego vuelve para continuar la configuración.",
      "it": "Shopcaisse richiede che il menu sia prima configurato nel POS. Upvendo non può creare il tuo catalogo in Shopcaisse — crea i tuoi articoli in Shopcaisse e poi torna per continuare la configurazione.",
      "pt": "O Shopcaisse exige que o seu menu esteja primeiro configurado no POS. O Upvendo não pode criar o seu catálogo no Shopcaisse — crie os seus artigos no Shopcaisse e depois volte para continuar a configuração."
    }
  }
]

Note: there is no per-store-count or "one application per store" branching in the connect flow. A single token can cover multiple stores, and Upvendo creates a location for each automatically when you paste it.


In-House Exclusivity

Shopcaisse is one of Upvendo's in-house channels (alongside Hendrickx and Vanhoutte, plus the in-house merchant-scoped channels Square, MplusKassa, and Lightspeed). A merchant can have only one in-house channel active at a time.

The legacy per-location connect path (enable) runs ThirdPartyIntegrationHelper::checkIntegrationPrerequisites, which rejects adding Shopcaisse when another in-house channel already exists for the merchant/location with HTTP 400 "Another in-house channel is already enabled for this merchant".

The current merchant-level paste-once connect (syncLocations) does not call that prerequisite check in the same place, so whether the exclusivity message fires on the paste-once path is not verified here — treat "one in-house channel per merchant" as the intended rule and disconnect any existing in-house channel first.


Prerequisites

Before pasting a token into Upvendo:

Upvendo side

  • A default branding profile is required. Only branding is enforced before connecting (ThirdPartyIntegrationHelper::checkRequiredProfiles requires a branding profile for Shopcaisse; payment and billing profiles are optional and can be set up later). The branding profile is also used as the default when Upvendo auto-creates a location for each Shopcaisse store.

Shopcaisse side (not verified here)

The following are configured on the Shopcaisse side and are not verifiable from Upvendo's code — confirm current requirements with Shopcaisse or your reseller:

  • A Public API / External Application must be available so the merchant can create an application and obtain a JWT bearer token. The token must grant at minimum company.*.items.read (read catalog) and store.*.orders.write (push orders); company.*.items.write is optional and enables pushing simple items created in Upvendo back to Shopcaisse.
  • An Order Management Module must be active on the POS device(s) that will receive orders, or Shopcaisse rejects order creation with a "…does not have an Order Management Module license…" error.
  • The iPad / POS device must be installed, logged in, and online so it can acknowledge incoming orders.
  • The menu must be fully configured in Shopcaisse (items, modifiers, VAT codes) — Upvendo reads it from there and cannot create the catalog in Shopcaisse.
  • Exact Shopcaisse subscription tiers, pricing, the permission-picker UI, and any iPad "Discovery Mode" gate are Shopcaisse-side and are not verified from Upvendo's code.

Obtaining the Shopcaisse Bearer Token

To connect Shopcaisse to Upvendo, the merchant creates an External Application in their Shopcaisse backoffice and copies the JWT it issues.

  1. Log in to the Shopcaisse backoffice.
  2. Open the External Application / Public API configuration and create an application.
  3. Set the Webhook URL to Upvendo's …/api/webhook/shopcaisse endpoint (so Shopcaisse can notify Upvendo of catalog and order changes). Upvendo verifies each delivery's HMAC signature.
  4. Grant the application access to the Company and Store(s) to connect, with at minimum:
    • company.*.items.read (catalog read)
    • store.*.orders.write (order create)
    • Optionally company.*.items.write (to push simple items created in Upvendo)
  5. Copy the generated JWT token.

The exact navigation path inside Shopcaisse's backoffice and the precise permission-picker UI are Shopcaisse-side and not verifiable from Upvendo's code. The Shopcaisse namespace of the token (prod/staging) decides which API host Upvendo talks to — use a token for the environment you intend to connect to.


Connecting in Upvendo

Route: /shopcaisse

The Shopcaisse page has three tabs: Settings (the connect form), Status (Sync Now / Test Connection / logs), and Locations (the mapped stores).

  1. Go to Settings → Brand and configure a default branding profile (required).
  2. Open the Shopcaisse page (/shopcaisse). When the merchant is not yet connected, it lands on the Settings tab with the connect form.
  3. Paste the Shopcaisse JWT bearer token into the token field and click Connect.
  4. Upvendo authenticates the token, fetches every store it covers, creates one Upvendo location per store, records the store → location map, and dispatches a catalog sync for each mapped location.
  5. The page switches to the Locations tab, which lists the mapped stores/locations and any warnings (for example, business hours not configured in Shopcaisse).

There is no manual store-selection step in the current UI. Locations are created automatically from the token's stores.


Setup Steps (JSON - for deterministic parsing)

The step lists below reflect the built-in guided-setup fallback (BASE_SETUP_STEPS.shopcaisse plus the shared KIOSK_CHANNEL_STEPS / ONLINE_ORDERING_CHANNEL_STEPS in the backoffice types.ts). Do not create a location by hand before connecting. Shopcaisse connect is merchant-level — pasting the token creates one Upvendo Location per Shopcaisse store — so the flow is connect, then review what was imported, the same shape as Square and MplusKassa. Shopcaisse previously shared the Hendrickx/Vanhoutte per-location template, which asked for a "Create Location" / "Select Location from Topbar" step first; following it produced a second, orphan location that no Shopcaisse store maps to, and while that location is selected the back office correctly shows no POS (no channel item, no In-House › Settings, no Online section). Those two steps are gone; complete_location ("Review Imported Locations") replaces them after the connect. The kiosk device step uses the canonical id kiosk_device, mirroring its has_kiosk_device setup-status flag; setupStepMappings.ts emits exactly one id per flag, and the retired configure_kiosk spelling is normalized on read by canonicalizeStepId (src/utils/emily/setupStepIds.ts). When Emily supplies dynamic steps during discovery, those are used instead.

setup_steps
{
  "Kiosk only": [
    {"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand"},
    {"id": "connect_pos", "title": "Connect Shopcaisse", "route": "/shopcaisse", "channel": "shopcaisse"},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Connecting created one Upvendo Location per Shopcaisse store. Verify the imported details; do not create a location by hand."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/shopcaisse"},
    {"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": "kiosk_device", "title": "Set up Kiosk & Activate Payment", "route": "/device-management/devices"},
    {"id": "payment_profile", "title": "Set up Payment Profile", "route": "/settings/payments", "optional_for_go_live": true},
    {"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "optional_for_go_live": true},
    {"id": "test_kiosk", "title": "Activate Kiosk & Test Order", "optional_for_go_live": true}
  ],
  "Online ordering only": [
    {"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand"},
    {"id": "connect_pos", "title": "Connect Shopcaisse", "route": "/shopcaisse", "channel": "shopcaisse"},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Connecting created one Upvendo Location per Shopcaisse store. Verify the imported details; do not create a location by hand."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/shopcaisse"},
    {"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": "payment_profile", "title": "Set up Payment Profile", "route": "/settings/payments", "optional_for_go_live": true},
    {"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "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}
  ],
  "Both": [
    {"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand"},
    {"id": "connect_pos", "title": "Connect Shopcaisse", "route": "/shopcaisse", "channel": "shopcaisse"},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Connecting created one Upvendo Location per Shopcaisse store. Verify the imported details; do not create a location by hand."},
    {"id": "sync_menu", "title": "Sync Menu", "route": "/shopcaisse"},
    {"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": "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": "payment_profile", "title": "Set up Payment Profile", "route": "/settings/payments", "optional_for_go_live": true},
    {"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "optional_for_go_live": true},
    {"id": "test_kiosk", "title": "Activate Kiosk & Test Order", "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}
  ]
}

Catalog & Order Sync

Catalog (Shopcaisse → Upvendo, one-way)

The catalog must exist in Shopcaisse first. After you connect, Upvendo automatically syncs each mapped location's catalog (and you can re-run it any time with Sync Now on the Status tab). The sync pulls items, combos/packs, modifier groups, product families (→ categories), VAT codes, item images, the seating plan (→ a "ShopCaisse Tables" section), stock for items flagged manageStock=true, and (once a store id has resolved for the location) a pull of Shopcaisse's CRM customers into Upvendo's customer list — see Customers below. Menu availability is derived from Shopcaisse's menus (items in the menu are activated, items not in it are deactivated; operator-set Unavailable/Hidden statuses are preserved).

Because Shopcaisse owns the catalog, Upvendo blocks creating modifier groups, modifiers, and categories (and deleting modifier groups/categories) for Shopcaisse-connected locations. Creating a simple item in Upvendo is allowed and is pushed back to Shopcaisse when its VAT resolves and the token grants company.*.items.write. On synced items, name, kitchen name, PLU, tax rate, and modifier-group links are locked; price and description are editable and push back to Shopcaisse.

Customers (Shopcaisse → Upvendo, one-way, pull-only)

Every catalog sync (Connect, Sync Now, and the company.items webhook re-sync) also pulls Shopcaisse's CRM customers for the mapped store — via ShopCaisseService::syncCustomersShopCaisseCustomerSyncService — and merges them into Upvendo's customer records, once a store id has resolved for the location:

  • Source: GET /v1/stores/{storeId}/customers (paginated). A record is skipped if Shopcaisse marks it deleted: true / active: false, or if it lacks a stable identity — Upvendo requires the Shopcaisse customer id and at least one of email or phone; an id with neither is dropped.
  • Matching order: an incoming customer is matched to an existing Upvendo customer for that merchant by, in order: (1) a previously-recorded Shopcaisse customer id, (2) email, (3) phone (tried only if the record has no email at all). If nothing matches, a new Upvendo customer is created.
  • Write behavior: the merchant-scoped customer fields (first/last name, email, phone, date of birth, company) are overwritten from Shopcaisse whenever Shopcaisse supplies a non-empty value; empty upstream fields never blank existing Upvendo data. The customer's global account record (shared across merchants) is filled only for fields that were blank — Shopcaisse never overwrites existing global account data.
  • This is pull-only — Upvendo never pushes customer changes back to Shopcaisse — and it is not a separate action; it always rides along with the catalog sync. Per-record failures are logged and skipped without failing the rest of the sync; a created/updated/skipped/failed summary is written to the Logs on the Status tab.

Orders (Upvendo → Shopcaisse)

Orders are pushed to Shopcaisse only after payment is confirmed, by ProcessOrderJob → ShopCaisseService::createOrder → POST /v1/stores/{storeId}/orders. A local idempotency guard stores the returned Shopcaisse order id on the transaction, so a retry/requeue does not re-create the same order.

Webhooks (Shopcaisse → Upvendo)

Upvendo exposes POST /api/webhook/shopcaisse, verified by HMAC signature. Shopcaisse sends company.items (triggers a catalog re-sync), store.orders (order lifecycle), and store.sales (sale finalization) events; Upvendo mirrors order status onto the matching transaction.

The in-Shopcaisse / on-iPad order lifecycle states and the storefront ordering UX are not verified from Upvendo's backend code here.


Disconnecting

Disconnecting Shopcaisse from the backoffice is disabled. The delete endpoint returns "Disconnecting ShopCaisse integration is disabled. Please contact administrator." To disconnect, contact Upvendo support / an administrator.


Troubleshooting

"I pasted my token but don't see my store(s)"

  • Confirm the External Application in Shopcaisse includes the store(s) and that the token was created with the correct company/store scope.
  • Run Test Connection on the Status tab — it reports how many stores the token covers.
  • Make sure the token matches the environment you intend to connect to (its namespace decides the API host).

"Connect fails with 'Missing required permissions'"

  • The token must grant both company.*.items.read and store.*.orders.write. Regenerate the application token with those permissions.

"My orders aren't reaching Shopcaisse"

  • Orders are pushed only after payment is confirmed — verify the transaction was paid.
  • The token needs store.*.orders.write, and the Shopcaisse POS device must have the Order Management Module active and be online.
  • Check the Logs on the Status tab for order push errors.
  • The catalog must be fully set up in Shopcaisse first — Upvendo cannot create it there.
  • Confirm the token has company.*.items.read, then re-run Sync Now.
  • Review the Logs for items skipped due to errors.

"401 / 403 — token expired"

  • The token was likely revoked or lacks permissions. Upvendo marks it expired. Generate a new token in Shopcaisse and re-enter it.

"Can I create menu items in Upvendo?"

  • Only simple items (pushed to Shopcaisse when VAT resolves and the token has company.*.items.write). Combos, packs, variants, modifier groups, modifiers, categories, and images must be built in Shopcaisse and synced in.

"Does Shopcaisse sync my customers into Upvendo?"

  • Yes. Every catalog sync (Connect, Sync Now, and the webhook re-sync) also pulls Shopcaisse's CRM customers for the mapped store and merges them into Upvendo's customer list — see Customers under Catalog & Order Sync. It's pull-only: Upvendo never pushes customer edits back to Shopcaisse. A record needs a Shopcaisse customer id plus at least an email or phone to be synced; records without either are skipped.

"Why do I have duplicate/merged customer records after a Shopcaisse sync?"

  • Upvendo matches an incoming Shopcaisse customer to an existing Upvendo customer, in order, by: (1) a previously-recorded Shopcaisse customer id, (2) email, (3) phone (only if the record has no email). If a customer's email or phone changed in Shopcaisse and no longer matches what Upvendo has on file, a new customer record can be created instead of updating the old one. Check the Logs on the Status tab for the sync's created/updated/skipped/failed counts.