Appearance
MplusKassa Onboarding (Reseller)
Overview
This is the onboarding flow for a reseller (partner) bringing a merchant onto Upvendo with the MplusKassa POS. MplusKassa is a merchant-scoped, in-house POS channel commonly used in the Netherlands and Belgium: one connection covers all of the merchant's branches, and the POS owns the catalog.
Reseller onboarding is a real, code-backed flow. A merchant that a reseller onboards is stamped with reseller_id and onboarding_type = 'reseller' on the merchant record (Merchant.php), as opposed to the default 'self'. Depending on the reseller's billing model, the platform either collects subscription fees (platform_collects) or the reseller invoices the merchant directly and there is no Stripe subscription or trial (reseller_collects, Reseller.php).
Reseller portal / partner dashboard mechanics (creating the merchant, assigning it to the partner, sending login credentials, switching into "client/merchant mode", reseller invoicing) live in the reseller/partner area of the back office and are summarised under "Reseller-Specific Surface" below. The POS-side steps in MplusKassa (configuring branches/workplaces in the MplusKassa back office, registering webhook subscriptions) are not verified here — they happen in MplusKassa, not in these repos.
Guided Setup Step List (MplusKassa)
The onboarding wizard ("Guided Setup", driven by Emily) shows an ordered checklist. In client mode the partner gets the same flow a self-serve merchant does: Emily asks the discovery questions below, then returns the checklist for the channel that was picked. The checklists mirror the merchant MplusKassa 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.mpluskassa list in src/components/dialogs/guided-setup/types.ts is not what a merchant sees.
Note: only Store Branding is actually enforced at connect time (see "What's enforced at connect"). The "Set up Business Profile" step (payments and Upvendo billing in one) carries
optional_for_go_live; the merchant can set it up later, but it is required before accepting customer payments. For a reseller on thereseller_collectsbilling model, the merchant's subscription billing is handled by the reseller directly, so platform billing/trial prompts are suppressed (AddChannelDialog.vue).
(See the Guided Setup feature doc for how completion is auto-detected from the merchant's real data via GET /api/back-office/setup-status.)
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 MplusKassa owning the catalog (one-way, read-only pull; see "MplusKassa Owns the Catalog" below and MplusKassa Integration Setup): with no catalog in MplusKassa 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 MplusKassa?",
"text_i18n": {
"nl": "Heeft de handelaar het menu ingesteld in MplusKassa?",
"fr": "Le marchand a-t-il configuré son menu dans MplusKassa ?",
"de": "Hat der Händler sein Menü in MplusKassa eingerichtet?",
"es": "¿El comercio tiene su menú configurado en MplusKassa?",
"it": "Il commerciante ha configurato il proprio menu in MplusKassa?",
"pt": "O comerciante tem o menu configurado no MplusKassa?"
},
"options": [
"Yes, menu is in MplusKassa",
"No, not yet"
],
"labels": {
"nl": {
"Yes, menu is in MplusKassa": "Ja, menu staat in MplusKassa",
"No, not yet": "Nee, nog niet"
},
"fr": {
"Yes, menu is in MplusKassa": "Oui, le menu est dans MplusKassa",
"No, not yet": "Non, pas encore"
},
"de": {
"Yes, menu is in MplusKassa": "Ja, das Menü ist in MplusKassa",
"No, not yet": "Nein, noch nicht"
},
"es": {
"Yes, menu is in MplusKassa": "Sí, el menú está en MplusKassa",
"No, not yet": "No, todavía no"
},
"it": {
"Yes, menu is in MplusKassa": "Sì, il menu è in MplusKassa",
"No, not yet": "No, non ancora"
},
"pt": {
"Yes, menu is in MplusKassa": "Sim, o menu está no MplusKassa",
"No, not yet": "Não, ainda não"
}
},
"abort_if": "No, not yet",
"abort_message": "The merchant needs their products and articles set up in MplusKassa first. MplusKassa owns the catalog and Upvendo only pulls it in read-only - items cannot be created in Upvendo. Please have them create their catalog in MplusKassa, then restart the onboarding.",
"abort_message_i18n": {
"nl": "De handelaar moet zijn producten en artikelen eerst in MplusKassa instellen. MplusKassa beheert de catalogus en Upvendo haalt die alleen read-only op — items kunnen niet in Upvendo worden aangemaakt. Laat hen hun catalogus in MplusKassa aanmaken en start daarna de onboarding opnieuw.",
"fr": "Le marchand doit d'abord configurer ses produits et articles dans MplusKassa. MplusKassa détient le catalogue et Upvendo ne le récupère qu'en lecture seule — les articles ne peuvent pas être créés dans Upvendo. Demandez-lui de créer son catalogue dans MplusKassa, puis recommencez l'intégration.",
"de": "Der Händler muss seine Produkte und Artikel zuerst in MplusKassa einrichten. MplusKassa besitzt den Katalog und Upvendo ruft ihn nur schreibgeschützt ab — Artikel können nicht in Upvendo erstellt werden. Lassen Sie ihn seinen Katalog in MplusKassa anlegen und starten Sie dann das Onboarding neu.",
"es": "El comercio debe configurar primero sus productos y artículos en MplusKassa. MplusKassa es dueño del catálogo y Upvendo solo lo obtiene en modo de solo lectura — los artículos no se pueden crear en Upvendo. Pídele que cree su catálogo en MplusKassa y luego reinicia el onboarding.",
"it": "Il commerciante deve prima configurare i propri prodotti e articoli in MplusKassa. MplusKassa possiede il catalogo e Upvendo lo recupera solo in sola lettura — gli articoli non possono essere creati in Upvendo. Chiedigli di creare il catalogo in MplusKassa e poi riavvia l'onboarding.",
"pt": "O comerciante precisa de configurar primeiro os seus produtos e artigos no MplusKassa. O MplusKassa é dono do catálogo e o Upvendo apenas o obtém em modo de leitura — os artigos não podem ser criados no Upvendo. Peça-lhe para criar o catálogo no MplusKassa 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": "Required before connecting MplusKassa; assigned to imported locations."},
{"id": "connect_pos", "title": "Connect MplusKassa", "route": "/mpluskassa", "note": "Enter the API ident and API secret (api_url and api_port are optional). Available only if the reseller's pos_providers includes 'mpluskassa'."},
{"id": "sync_locations", "title": "Sync Locations", "route": "/mpluskassa", "note": "MplusKassa is merchant-scoped: one connection imports all branches as locations."},
{"id": "complete_location", "title": "Complete Location Details", "route": "/settings/locations", "note": "MplusKassa supplies only the branch name; add address, coordinates, hours, etc. Not using a branch (e.g. the POS demo branch)? Set it to Inactive instead."},
{"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": "Optional at connect, but required 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": "Required before connecting MplusKassa; assigned to imported locations."},
{"id": "connect_pos", "title": "Connect MplusKassa", "route": "/mpluskassa", "note": "Enter the API ident and API secret (api_url and api_port are optional). Available only if the reseller's pos_providers includes 'mpluskassa'."},
{"id": "sync_locations", "title": "Sync Locations", "route": "/mpluskassa", "note": "MplusKassa is merchant-scoped: one connection imports all branches as locations."},
{"id": "complete_location", "title": "Complete Location Details", "route": "/settings/locations", "note": "MplusKassa supplies only the branch name; add address, coordinates, hours, etc. Not using a branch (e.g. the POS demo branch)? Set it to Inactive instead."},
{"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": "Optional at connect, but required 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": "Required before connecting MplusKassa; assigned to imported locations."},
{"id": "connect_pos", "title": "Connect MplusKassa", "route": "/mpluskassa", "note": "Enter the API ident and API secret (api_url and api_port are optional). Available only if the reseller's pos_providers includes 'mpluskassa'."},
{"id": "sync_locations", "title": "Sync Locations", "route": "/mpluskassa", "note": "MplusKassa is merchant-scoped: one connection imports all branches as locations."},
{"id": "complete_location", "title": "Complete Location Details", "route": "/settings/locations", "note": "MplusKassa supplies only the branch name; add address, coordinates, hours, etc. Not using a branch (e.g. the POS demo branch)? Set it to Inactive instead."},
{"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": "Optional at connect, but required 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}
]
}Integration Mechanics
Merchant-Scoped, Single In-House POS
MplusKassa is merchant-scoped: one connection covers all branches, and the merchant's MplusKassa branches are imported as Upvendo locations on connect/sync.
A merchant may have only one in-house POS channel at a time. MplusKassa is one of the in-house POS channels (alongside Square, Lightspeed, Hendrickx, Vanhoutte, Shopcaisse). The backend hard-blocks connecting MplusKassa if another in-house channel already exists for the merchant — ThirdPartyIntegrationHelper::checkForInhouseChannel aborts with HTTP 400 ("Another in-house channel is already enabled for this merchant"), and the connect dialog disables other in-house channels with reason merchant_has_pos (OrderingChannelService::inHouseChannels, AddInhouseChannelDialog.vue). This is the real "in-house exclusivity" rule: it is mutual exclusivity among in-house POS channels, not POS-vs-in-house.
MplusKassa Owns the Catalog (Read-Only Sync)
The menu/catalog sync is one-directional: MplusKassa -> Upvendo. The synced fields are locked in Upvendo so they cannot drift:
- Items:
name,description,price,plu,modifier_group_ids,allergens,dietary_preferencesare locked (Item.phpgetUneditableFields, enforced inItemRepository::sanitizeData). - Categories:
name,descriptionare locked (Category.php). - Modifier groups:
name,is_mandatory,allow_select_more_than_one,allow_same_modifier_more_than_one,min_selected,modifier_ids,pricing,modifier_pricesare locked (ModifierGroup.phpgetUneditableFields). Note:max_selected("Maximum of modifiers a customer can select") is left editable for MplusKassa groups and syncs back to MplusKassa — it is the one modifier-group field a merchant can change in Upvendo.
Fields that do not come from MplusKassa (item images, availability/visibility, channel settings) stay editable.
There is no production bulk push-back of the catalog from Upvendo to MplusKassa: the catalog write APIs exist only on a developer/test route (routes/api/dev/mpluskassa.php, loaded only in the test environment). One narrow exception is real in production: modifier-group edits (preparation-method / article-alteration groups) are pushed/updated/deleted back to MplusKassa by SyncMplusKassaMenuJob when a menu entity changes (IntegrationTrait::syncToThirdParty -> SyncMplusKassaMenuJob). Items and categories are never pushed back.
Pull Mapping (MplusKassa -> Upvendo)
The import maps:
| MplusKassa | Upvendo | Notes |
|---|---|---|
| ArticleGroup (TurnoverGroup fallback) | Category | Source of categories; saveArticleGroupAsCategory / syncTurnoverGroupsAsCategories |
| Article | Item | The sellable item; external id = articleNumber |
| PreparationMethodGroup | ModifierGroup | Pull import (push-back exception above) |
| ArticleAlterationsGroup | ModifierGroup | Pull import (push-back exception above) |
| Branch | Location | Auto-imported on connect/sync |
| VAT code (per article) | Tax rate | Auto-created as MPLUS_VAT_{code}, tax method inclusive |
Stock (amountFree) | Inventory | Pull only |
Modifier-group external IDs are stored as a composite string {type}_{number} (e.g. preparation_method_5, article_alteration_10); the number is extracted via MplusKassaTrait::extractGroupNumberFromCompositeId and is never cast directly to an integer.
Actions
Connect MplusKassa
Route: /mpluskassa
Enter the MplusKassa API connection details and click Connect. The connect request (InitiateMplusKassaRequest, posted from MplusKassaForm.vue / MplusKassaChannelDialog.vue) carries these fields:
- API ident -- payload key
ident(required at connect) - API secret -- payload key
secret(required at connect) - API URL -- payload key
api_url(optional; defaults to the standard MplusKassa endpoint) - API port -- payload key
api_port(optional integer)
The Connect button is disabled until ident and secret are entered. On connect, Upvendo imports the merchant's MplusKassa branches as locations.
What's enforced at connect
Only the branding profile is enforced. MplusKassaIntegrationService::initiate calls ThirdPartyIntegrationHelper::checkIntegrationPrerequisites, which (in checkRequiredProfiles) requires at least one branding profile for MplusKassa and aborts with HTTP 400 ("Missing required profiles") if none exists. The business profile is not checked at connect — the merchant can add it afterwards, and needs it before accepting customer payments.
Configure Branch and Workplaces
Once connected, choose the branch and the workplaces that orders are injected into (InitiateMplusKassaRequest):
- Branch -- payload key
branch_number - Online ordering workplace -- payload key
online_workplace_number; where Online Ordering orders are sent - Table QR workplace -- payload key
qr_workplace_number; optional (clearable in the UI). When not set, Table QR orders use the online workplace (the UI states "Table QR orders will use the online workplace").
Sending different channels to different workplaces improves POS reporting.
Set the Daily Sync Time
Set a sync time for the automatic daily catalog/inventory sync. The time is restricted to a whole or half hour — minutes must be 00 or 30 (validated server-side by the regex ^([01]?[0-9]|2[0-3]):(00|30)$ in InitiateMplusKassaRequest; the form also snaps any other minute value to 00/30). The value is stored as the merchant's local wall-clock time, not converted to UTC, so a 03:30 sync stays at 03:30 across daylight-saving changes. The form displays the resolved timezone as a caption under the field; it is not a merchant-selectable option.
Sync Now
Triggering a sync pulls new and changed products/articles from MplusKassa using sync markers, maps ArticleGroups (TurnoverGroups fallback) to Categories and Articles to Items, downloads images, imports preparation-method / article-alteration groups as modifier groups, and updates VAT rates and stock. The sync refuses to start if one is already in progress.
Complete Location Setup
After branches import, go to Settings -> Locations (/settings/locations) to complete fields the POS does not provide (address coordinates, business hours, delivery zones, etc.). Imported locations are assigned the default branding profile (required); the default billing/business profiles are assigned when present (optional, non-fatal).
Disconnect (administratively disabled)
Disconnecting is administratively disabled. The back office still surfaces a disconnect option, but POST /mpluskassa/disable short-circuits in MplusKassaIntegrationController::disable and returns HTTP 400 with: "Disconnecting MplusKassa integration is disabled. Please contact administrator." The teardown logic in the service is never reached through this endpoint.
Reseller-Specific Surface
These reseller behaviours are grounded in code (backend app/RawModels/Reseller.php, app/Services/BackOffice/ResellerService.php, app/Services/Subscription/ResellerSubscriptionService.php, routes/api/backoffice/resellers.php; backoffice resellers/, clients/, partner pages):
- Roles: reseller users have
reseller-owner/reseller-administrator/reseller-reproles (app/Constants/Roles.php). The top global role isglobal-administrator(not "super-admin"). - Creating a merchant:
POST /partner/merchants -> ResellerController::createMerchant(authorised for global users) creates the merchant + owner user, stampsreseller_idandonboarding_type = 'reseller', and appends the merchant to the partner user'svendor_ids.POST /partner/merchants/{merchantId}/send-credentialsresets the owner password (setsmust_change_password) and emails credentials. - Managing multiple merchants: there is no "impersonate / login as" feature. Instead the partner/admin uses client/merchant mode switching (
switchMerchant, areseller_idJWT claim viaAuthService) plus a partner dashboard (/dashboard/partner,getPartnerMerchants) and reseller-detail tabs (Users, Merchants assign/unassign, Subscriptions/Invoices, Payouts). - Billing model drives subscription handling (
Reseller.php):platform_collects(platform bills, with a reseller-onboarding grace period) vsreseller_collects(reseller invoices the merchant directly — no Stripe subscription, no trial;ResellerSubscriptionService). Onreseller_collects, the onboarding channel dialog hides the free-trial badge (AddChannelDialog.vue). - Reseller's POS provider set:
GET /merchant/reseller-providers -> getMerchantResellerProvidersreturns only the reseller's allowed POS providers (reseller->getPosProviders()); the auth payload exposesreseller_providersandbilling_model. This is what scopes which POS options the reseller's merchant sees during onboarding.
Business Rules
- MplusKassa is the source of truth for the catalog, prices, VAT and stock; the synced fields are locked in Upvendo.
- The menu sync is one way (MplusKassa -> Upvendo) for items and categories. The only production write-back is modifier groups (preparation-method / article-alteration), pushed by
SyncMplusKassaMenuJob. - Inventory is one way (MplusKassa -> Upvendo): the sync reads
amountFreeper article per branch. - Branch-specific price deviations are honoured: a branch's article price overrides the default for that location.
- A merchant may have only one in-house POS channel; connecting MplusKassa is blocked (HTTP 400) if another in-house channel exists.
- Only the branding profile is enforced at connect; the business profile (including billing) can be added later, and is required before accepting customer payments.
- Connect requires
identandsecret;api_urlandapi_portare optional. - Sync time must be a whole or half hour (
:00or:30), and is interpreted as the merchant's local time (stored as a local wall-clock, so it does not drift with daylight saving). - Disconnect is administratively disabled (HTTP 400, "contact administrator").
- Orders flow one way (Upvendo -> MplusKassa) and are forwarded automatically once an order reaches a syncable status; there is no manual-acceptance hold. VAT method sent to MplusKassa is
INCLUSIVE. (See the shared MplusKassa integration doc for the full order-forwarding, webhook, external-payment and relation-pricing detail.) - Reseller-onboarded merchants carry
reseller_id+onboarding_type = 'reseller'; on thereseller_collectsbilling model there is no platform Stripe subscription or trial.
Prerequisites
Surfaced in the guided-setup checklist (only branding is enforced at connect):
Branding Profile (Required at connect)
- Go to Settings -> Brand (
/settings/brand). - Configure the store's branding (logo, colours, etc.).
- The default branding profile is assigned to imported locations. Connect is blocked (HTTP 400) if no branding profile exists.
Business Profile (Optional — needed to accept customer payments)
The guided-setup checklist calls this step "Set up Business Profile". It covers payments AND billing (step 4).
- Go to Settings -> Business profile (
/settings/payments). - Set up payment providers (e.g. Stripe for Online Ordering, Viva Wallet for Kiosk terminals).
- Configure payment methods (iDEAL is common in the Netherlands; cards; Bancontact for Belgian customers).
- Open the profile's Billing tab (the old
/settings/billinglink forwards there; there is no separate Billing menu entry). If it shows "No billing profile yet", click Create billing profile and complete the billing information. A reseller-billed merchant sees "Billed by your reseller" there instead. On areseller_collectsreseller, the reseller invoices the merchant directly, so platform billing is handled by the reseller. - Select the business profile on each location: Settings → Locations → edit → Business Profile. The location's billing follows from it (shown as "Billed through billing profile …").
Netherlands / Belgium Notes
Payment Methods
Common choices for Dutch and Belgian customers:
- iDEAL -- popular in the Netherlands (via Stripe)
- Card -- Visa, Mastercard
- Bancontact -- for Belgian customers
VAT (BTW)
There is no tax-mapping screen. Each MplusKassa article carries its own VAT code/percentage, and Upvendo auto-creates a matching tax rate (MPLUS_VAT_{code}) with the inclusive tax method on import. Branches can be in the Netherlands or Belgium; country, timezone and language are derived from the MplusKassa branch.
FAQs
Q: Can the merchant edit menu items or prices in Upvendo? A: Not for fields that come from MplusKassa. MplusKassa owns the catalog, so item name, description, price, PLU, modifier groups, allergens and dietary preferences (and category name/description) are locked. Make those changes in MplusKassa, then sync.
Q: Is the menu sync bidirectional? A: No for items and categories — it is one way, MplusKassa -> Upvendo. The only production write-back is modifier groups (preparation-method / article-alteration), pushed by a background job when a menu entity changes.
Q: What does the reseller actually need before connecting MplusKassa? A: A branding profile (the only prerequisite enforced at connect). The business profile (including billing) can be set up later, but is required before accepting customer payments.
Q: Can a merchant have MplusKassa and another POS at the same time? A: No. Only one in-house POS channel per merchant; connecting MplusKassa is blocked if another in-house channel is already enabled.
Q: How does a reseller create the merchant? A: Through the partner area (POST /partner/merchants), which creates the merchant and owner user, stamps it with the reseller ID, and can email login credentials. Managing the merchant afterwards uses client/merchant-mode switching, not impersonation.
Q: Who bills the merchant? A: It depends on the reseller's billing model. platform_collects = the platform bills the merchant (with a reseller-onboarding grace period). reseller_collects = the reseller invoices the merchant directly, with no platform Stripe subscription and no trial.
Q: Can I disconnect MplusKassa from the back office? A: No. The disconnect option is administratively disabled and returns an error asking you to contact an administrator.
Troubleshooting
Connect is rejected
- "Missing required profiles" -> create a branding profile first (Settings -> Brand). Branding is the only profile enforced at connect.
- "Another in-house channel is already enabled for this merchant" -> the merchant already has an in-house POS channel; only one is allowed.
- Connect button stays disabled -> enter both API ident (
ident) and API secret (secret).
Locations not imported
- Check that the MplusKassa account has active branches.
- After import, complete the missing fields in Settings -> Locations.
- If a branch was added after the initial connection, run a sync to detect it.
Menu/price changes not appearing
- Make the change in MplusKassa, then sync (the field is locked in Upvendo).
- Confirm the sync ran (check logs / sync markers); sync won't start if one is already in progress.
Sync time won't save
- The sync time must be a whole or half hour (minutes
00or30). Other values are rejected (or snapped to the nearest in the form).
Can't disconnect
- Disconnect is administratively disabled. Contact an administrator.