Skip to content

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:127 and config/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

CapabilitySquareLightspeed K-SeriesMplusKassaKassanet (Hendrickx / Vanhoutte)Shopcaisse
Connect scopeMerchantMerchantMerchantPer locationPer location
Countries (config)US, CA, GB, AU, JP, IE, FR, ESBE, NL, FR, DE, GB, US, CA, AU, IE, ESBE, NLBEBE, FR
Payment handlingSquare processes paymentsSeparate providerSeparate providerSeparate providerSeparate provider
Auth methodOAuth 2.0 (no PKCE)OAuth 2.0 (no PKCE, Basic-auth token exchange)API ident + secret (SOAP)HMAC-SHA256 request signing + Blowfish payload encryptionBearer token
Menu source of truthTwo-way (sync arbitration)POS for categories/modifiers; two-way for itemsPOS (pull-only)POS (pull-only)POS (pull-only)
Order push (Upvendo → POS)YesYesYesYes (Blowfish-encrypted)Yes
Order ingest (POS → Upvendo)Not yet (deferred)UnverifiedUnverifiedUnverifiedUnverified
Inventory / availability syncTwo-wayPOS → Upvendo (webhook)POS → Upvendo (pull)UnverifiedUnverified
Tables / floor sectionsNoNoYes (pull)Yes (sections)Unverified
Webhook supportYesYesYes (incl. external payment)UnverifiedYes

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 no location_id filter.
    • Square: app/Services/BackOffice/SquareIntegrationService.php resolves the integration by vendor_id + provider.
    • MplusKassa: app/Services/BackOffice/MplusKassaIntegrationService.php resolves by vendor_id + provider.
    • Lightspeed: app/Services/BackOffice/LightspeedKSeriesIntegrationService.php is explicitly commented "Merchant-scoped — no location argument. Location mapping is a post-OAuth step."
  • 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.php syncLocations() / 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.

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

IntegrationPayment processingWhat this means
SquareSquare processes paymentsSquare 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).
LightspeedSeparate providerUpvendo channels need their own configured payment provider; Lightspeed itself is not a payment provider in PaymentProviders.php.
MplusKassaSeparate providerSame as Lightspeed. MplusKassa additionally relays external payment events (e.g. POS PIN terminal) to Upvendo via webhooks (see below).
Kassanet (Hendrickx/Vanhoutte)Separate providerThe POS handles in-store payments; Upvendo channels need their own payment provider.
ShopcaisseSeparate providerThe 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 Square

Lightspeed 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 locations

MplusKassa — 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 + body with the app signature (getSignature(), lines 129-139).
  • The request body is encrypted with Blowfish (CBC, padding disabled, 8 null-byte IV) via phpseclib3\Crypt\Blowfish when 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)

Confirmed from code; entries that could not be confirmed are marked Unverified rather than asserted.

CapabilitySquareLightspeedMplusKassaKassanetShopcaisse
CategoriesYesYesYesYesYes
ItemsYesYesYesYesYes (SIMPLE)
ModifiersYesYesYesYesNo (create blocked)
ImagesYesYesUnverifiedUnverifiedUnverified
PricesYesYesYesYesYes
Display groupsNoNoUnverifiedYes (auto-created)No
Tax ratesYesUnverifiedUnverifiedYes (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_only in 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_only in 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 POS

Pattern 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.