Appearance
POS Integration Comparison Guide
Overview
Upvendo connects to several POS systems and food-delivery channels. Each in-house POS integration has a different scope, authentication method, and sync direction. This guide compares the in-house POS integrations so merchants and support staff can understand which fits each situation.
The in-house POS integrations defined in code are: Hendrickx, Vanhoutte (both Belgian, connected through the Kassanet connector), Shopcaisse, Square, MplusKassa, and Lightspeed K-Series. Source: config/pos-providers.php:18-211 and app/Helpers/ThirdPartyIntegrationHelper.php:16,18. That config block also defines a first-party upvendo provider ('first_party' => true, staged 'test_only' => true) — it is the in-house register, not a third-party integration, and the staging flag keeps it off onboarding for live merchants (test-flagged merchants are offered it).
Food-delivery / online channels (Deliveroo, Uber Eats, Shopify, Trivec) are a separate channel class and are out of scope for this POS comparison. They are listed under ONLINE_CHANNELS at app/Helpers/ThirdPartyIntegrationHelper.php:20.
Note: In code today, both Shopcaisse and Lightspeed K-Series are flagged
'test_only' => true(config/pos-providers.php:127andconfig/pos-providers.php:165), meaning they are intended for testing/staging environments and should not be surfaced in production until that flag is removed.
At a Glance
| Capability | Square | Lightspeed K-Series | MplusKassa | Kassanet (Hendrickx / Vanhoutte) | Shopcaisse |
|---|---|---|---|---|---|
| Connect scope | Merchant | Merchant | Merchant | Per location | Per location |
| Countries (config) | US, CA, GB, AU, JP, IE, FR, ES | BE, NL, FR, DE, GB, US, CA, AU, IE, ES | BE, NL | BE | BE, FR |
| Payment handling | Square processes payments | Separate provider | Separate provider | Separate provider | Separate provider |
| Auth method | OAuth 2.0 (no PKCE) | OAuth 2.0 (no PKCE, Basic-auth token exchange) | API ident + secret (SOAP) | HMAC-SHA256 request signing + Blowfish payload encryption | Bearer token |
| Menu source of truth | Two-way (sync arbitration) | POS for categories/modifiers; two-way for items | POS (pull-only) | POS (pull-only) | POS (pull-only) |
| Order push (Upvendo → POS) | Yes | Yes | Yes | Yes (Blowfish-encrypted) | Yes |
| Order ingest (POS → Upvendo) | Not yet (deferred) | Unverified | Unverified | Unverified | Unverified |
| Inventory / availability sync | Two-way | POS → Upvendo (webhook) | POS → Upvendo (pull) | Unverified | Unverified |
| Tables / floor sections | No | No | Yes (pull) | Yes (sections) | Unverified |
| Webhook support | Yes | Yes | Yes (incl. external payment) | Unverified | Yes |
Sources are cited throughout the sections below. Cells marked Unverified could not be confirmed from the back-end or back-office repositories and may live elsewhere (storefront, kiosk, proxy).
Detailed Comparison
Integration Scope
Scope is defined by two arrays in app/Helpers/ThirdPartyIntegrationHelper.php:
IN_HOUSE_MERCHANT_SCOPED_CHANNELS = [Square, MplusKassa, Lightspeed](line 16)IN_HOUSE_CHANNELS = [Hendrickx, Vanhoutte, Shopcaisse]— location-scoped (line 18)
Merchant-scoped (Square, MplusKassa, Lightspeed K-Series)
- One connection covers the whole account. The integration record is keyed by
vendor_id(the merchant), with nolocation_idfilter.- Square:
app/Services/BackOffice/SquareIntegrationService.phpresolves the integration byvendor_id+ provider. - MplusKassa:
app/Services/BackOffice/MplusKassaIntegrationService.phpresolves byvendor_id+ provider. - Lightspeed:
app/Services/BackOffice/LightspeedKSeriesIntegrationService.phpis explicitly commented "Merchant-scoped — no location argument. Location mapping is a post-OAuth step."
- Square:
- Location handling differs between the three:
- Square auto-imports Square locations into Upvendo (
app/Services/BackOffice/Square/SquareLocationSync.php). - MplusKassa auto-imports POS branches as Upvendo locations (
MplusKassaIntegrationService.phpsyncLocations()/branches()endpoint). - Lightspeed does not auto-import locations. The Upvendo location must exist first and is then mapped to a Lightspeed business location ID via a
createMapping($upvendoLocationId, $businessLocationId)step.
- Square auto-imports Square locations into Upvendo (
Location-scoped (Kassanet/Hendrickx/Vanhoutte, Shopcaisse)
- Each location is connected separately; the conflict check is performed against
location_id(ThirdPartyIntegrationHelper.php:122-135). - Connect each location from the In-House settings page after selecting the target location.
Only one in-house POS per merchant/location
You cannot run two in-house POS integrations at the same time. checkForInhouseChannel() (ThirdPartyIntegrationHelper.php:102-140) aborts with 400 "Another in-house channel is already enabled for this merchant" if:
- a merchant-scoped POS is already connected anywhere on the account, or
- a location-scoped POS is already connected on the target location.
Connect-time prerequisite
The only profile enforced at connect time is the branding profile, and only for Hendrickx, Vanhoutte, Shopcaisse, and MplusKassa (ThirdPartyIntegrationHelper.php:149-175). Payment and billing profiles are optional and can be set up later (see the comment at line 153). Square and Lightspeed are not in the branding-required list.
Payment Handling
| Integration | Payment processing | What this means |
|---|---|---|
| Square | Square processes payments | Square is both a POS and a payment provider. It is one of the three payment providers in app/Enums/PaymentProviders.php (cases viva, stripe, square). It builds Square orders and handles Square Terminal checkouts (app/Services/BackOffice/Square/SquareOrderBuilder.php, SquareTerminalAndPayments.php) and online payments (app/Http/Controllers/Api/OnlineOrderingController.php). |
| Lightspeed | Separate provider | Upvendo channels need their own configured payment provider; Lightspeed itself is not a payment provider in PaymentProviders.php. |
| MplusKassa | Separate provider | Same as Lightspeed. MplusKassa additionally relays external payment events (e.g. POS PIN terminal) to Upvendo via webhooks (see below). |
| Kassanet (Hendrickx/Vanhoutte) | Separate provider | The POS handles in-store payments; Upvendo channels need their own payment provider. |
| Shopcaisse | Separate provider | The POS handles in-store payments; Upvendo channels need their own payment provider. |
Key point: the three payment providers in code are Viva Wallet, Stripe, and Square (app/Enums/PaymentProviders.php). Only Square doubles as a POS integration that also processes payments. Stripe and Viva Wallet are payment-only and are configured independently of the POS integration.
No mutual-exclusion logic was found that disables Stripe or Viva Wallet when Square is connected.
checkForInhouseChannel()only blocks a second POS, not a payment provider. A claim that "Square replaces Stripe/Viva" could not be confirmed in code and has been removed.
Sync Direction and Data Flow
Square — two-way menu sync
Square is the only POS integration with genuine two-way catalog sync. A per-entity arbitration step (app/Services/BackOffice/Square/SquareSyncDirectionTrait.php:33-154, determineSyncDirection() returning upvendo_to_square | square_to_upvendo | skip) decides direction by comparing change timestamps/hashes. The category, item, modifier, location, and inventory sync classes all consult it and either pull from Square or push to Square accordingly (e.g. SquareCategorySync.php:170-193, SquareItemSync.php:397-409, SquareInventorySync.php).
text
Square POS <-- two-way --> Upvendo
- Catalog (categories, items, modifiers, images): two-way, direction arbitrated per entity
- Locations: two-way (Square is the source on connect)
- Inventory: two-way
- Orders: Upvendo -> Square (push). POS-originated order ingest into Upvendo is NOT implemented yet
(deferred; see SquareWebhookService.php:522-524).
- Payments: processed by SquareLightspeed K-Series — two-way items, pull-only categories/modifiers
For items, Lightspeed K-Series is two-way. Upvendo imports items inbound during menu sync and also pushes item writes back to K-Series: item create (POST /items/v1/items), item edit (PUT /items/v1/items/{id}), rich data (PUT /i/richItem/...) and images (PATCH), via LightspeedKSeriesItemPushService, triggered on item save. Categories / accounting-groups and modifier groups remain pull-only — the K-Series Menu API is GET-only for those, so they are imported inbound but cannot be pushed back. Orders are pushed real-time; menu/catalog sync runs once daily at 07:01 by default (or settings.sync_time), read as a local wall-clock in settings.sync_timezone rather than UTC.
text
Lightspeed POS <-- items two-way --> Upvendo
Lightspeed POS ------> Upvendo (categories / accounting-groups, modifier groups: pull-only)
Upvendo ------> Lightspeed (orders, push)
- Items: Lightspeed <-> Upvendo (import inbound + push item create/edit/rich-data/images
outbound via LightspeedKSeriesItemPushService, on item save)
- Categories / accounting-groups + modifier groups: Lightspeed -> Upvendo only
(K-Series Menu API is GET-only)
- Orders: Upvendo -> Lightspeed (to-go / local), real-time. Status flows back via webhook.
- Item availability (inventory): Lightspeed -> Upvendo via ITEM webhook
- Locations: created in Upvendo, then mapped to Lightspeed business locationsMplusKassa — pull-only menu (limited modifier write-back)
MplusKassa is primarily import/pull: products, categories, modifier groups, inventory, and tables are read from the POS into Upvendo. There is partial modifier-group write-back (pushModifierGroupToMplusKassa() at MplusKassaIntegrationService.php:6340 and updateArticleAlterationsGroupInMplusKassa() at :6417), but the broader modifier-group sync path is still one-way: the code at MplusKassaIntegrationService.php:4965 states "we only pull from MplusKassa (one-way sync)" with push marked TODO. Treat MplusKassa as pull-only with limited alteration-group write-back, not full bidirectional menu sync.
text
MplusKassa POS ------> Upvendo (catalog, inventory, tables — pull)
Upvendo ------> MplusKassa (orders; limited modifier/alteration-group writes)
- Menu: MplusKassa -> Upvendo (pull); limited alteration-group push back
- Orders: Upvendo -> MplusKassa (SOAP)
- Inventory: MplusKassa -> Upvendo (pull; StockService getStock)
- Tables: MplusKassa -> Upvendo (syncTables, per branch)
- External payments: MplusKassa -> Upvendo via webhook (startExternalPayment / pollExternalPayment,
WebhookService.php:25-26, :512-546)Kassanet (Hendrickx / Vanhoutte) — pull-only menu, encrypted orders
Hendrickx and Vanhoutte are two distinct providers (Constants::HENDRICKX, Constants::VANHOUTTE) that share the Kassanet connector (config/pos-providers.php:22,46, both 'connector' => 'kassanet'). The menu must pre-exist in the POS and is imported (pull-only). Orders are pushed back to Kassanet, with the JSON payload encrypted.
text
Kassanet POS ------> Upvendo (menu import, pull-only)
Upvendo ------> Kassanet POS (orders, encrypted)
- Menu: Kassanet -> Upvendo (import only)
- Orders: Upvendo -> Kassanet via CreateOrder (AbstractKassanetService.php:1501)
- Display groups: auto-created during import (saveIntoUpvendoDisplayGroup,
AbstractKassanetService.php:3291; ImportKassanetDisplayGroupsJob)
- Table sections: supported (table-section / physical-table handling in AbstractKassanetService.php)Auth and encryption (AbstractKassanetService.php):
- Request signing uses HMAC-SHA256 over
date + bodywith the app signature (getSignature(), lines 129-139). - The request body is encrypted with Blowfish (CBC, padding disabled, 8 null-byte IV) via
phpseclib3\Crypt\Blowfishwhen encryption is enabled (encryptBlowfish(), line 146; applied in the request builder at line 319).
Shopcaisse — pull-only menu, token auth
text
Shopcaisse POS ------> Upvendo (catalog, pull-only inbound)
Upvendo ------> Shopcaisse (orders; limited SIMPLE-item create)
- Catalog: Shopcaisse -> Upvendo. Read-only inbound; creating modifier groups, modifiers,
or categories in Upvendo is blocked (config/pos-providers.php:94-106). Only SIMPLE item
creates are pushed when VAT resolves.
- Orders: Upvendo -> Shopcaisse (pushed by ProcessOrderJob)
- Auth: Bearer token (Http::withToken, ShopCaisseService.php)
- Webhooks: store.orders / store.sales webhooks are processed (ShopCaisseService.php;
VerifyShopCaisseWebhook middleware)Menu Import Capabilities
Confirmed from code; entries that could not be confirmed are marked Unverified rather than asserted.
| Capability | Square | Lightspeed | MplusKassa | Kassanet | Shopcaisse |
|---|---|---|---|---|---|
| Categories | Yes | Yes | Yes | Yes | Yes |
| Items | Yes | Yes | Yes | Yes | Yes (SIMPLE) |
| Modifiers | Yes | Yes | Yes | Yes | No (create blocked) |
| Images | Yes | Yes | Unverified | Unverified | Unverified |
| Prices | Yes | Yes | Yes | Yes | Yes |
| Display groups | No | No | Unverified | Yes (auto-created) | No |
| Tax rates | Yes | Unverified | Unverified | Yes (taxRate on product) | Unverified |
Decision Guide
Choose Square if
- You want a single provider for menu, orders, and payments (Square is the only POS in code that also processes payments).
- You want two-way catalog sync rather than pull-only.
- You operate in a Square-supported country (config lists US, CA, GB, AU, JP, IE, FR, ES).
Choose Lightspeed K-Series if
- You run Lightspeed Restaurant (K-Series) as your POS and want Upvendo as a customer-facing ordering layer on top of it.
- You want item edits to sync both ways (Upvendo pushes item create/edit/rich-data/images back to K-Series), while categories/accounting-groups and modifier groups stay pull-only (Lightspeed remains their source of truth).
- You will map each Upvendo location to its Lightspeed business location after connecting.
- Note: currently
test_onlyin config.
Choose MplusKassa if
- You use MplusKassa as your POS (config countries: BE, NL).
- You want POS branches imported automatically as Upvendo locations and table import.
- You need external-payment webhook handling (e.g. POS PIN-terminal payment events).
- You accept a pull-only menu (with only limited alteration-group write-back).
Choose Kassanet (Hendrickx / Vanhoutte) if
- You are in Belgium and use a Hendrickx or Vanhoutte (Vectron-based) POS.
- You want to import an existing POS menu into Upvendo for kiosk/online ordering.
- You need table-section support for in-house ordering.
Choose Shopcaisse if
- You use Shopcaisse as your POS (config countries: BE, FR).
- You need basic catalog import and order forwarding.
- Note: currently
test_onlyin config, and its catalog is read-only inbound (only SIMPLE item creates push back).
Common Setup Patterns
Pattern 1: POS integration + online ordering (non-Square)
text
POS (pull-only menu) + a configured payment provider (Stripe / Viva Wallet)
|
v
Menu imported from POS -> shown on Online Ordering
|
v
Customer orders online and pays via the configured provider
|
v
Order pushed to POSPattern 2: Square all-in-one
text
Square (POS + payments)
|
v
Catalog two-way synced with Square -> Upvendo channels
|
v
Payments processed by Square
|
v
Orders pushed to Square (POS-originated order ingest not yet implemented)Migration Considerations
Switching POS integrations
- Only one in-house POS can be active per merchant (merchant-scoped) or per location (location-scoped). Disconnect the current one before connecting another (
checkForInhouseChannel). - Behaviour of leftover imported menu items, re-mapping, and order-history retention after a switch is Unverified from these repos and should be confirmed before stating it to a merchant.
Multiple locations with different POS systems
- Merchant-scoped integrations (Square, MplusKassa, Lightspeed) cover the whole account, so you cannot also add a second merchant-scoped POS.
- Location-scoped integrations (Hendrickx, Vanhoutte, Shopcaisse) are connected per location, so different locations can use different location-scoped POS systems — subject to the one-POS-per-location rule.