Skip to content

Square Onboarding Flow

Overview

Square is a merchant-scoped POS integration that includes both catalog/order management AND payment processing. One OAuth connection covers the whole merchant account; all Square locations are imported as Upvendo locations. Because Square processes payments itself, no separate payment provider is needed — Square handles kiosk payments (Square Terminal) and online-ordering payments (Square Web Payments SDK).

Square syncs menu, locations and inventory. For each entity (location, category, modifier group, item, variant group, inventory count) the system decides per-entity whether to push (Upvendo → Square), pull (Square → Upvendo), or skip, based on timestamps and a content hash. You can either sync an existing Square catalog into Upvendo, or build items in Upvendo and push them to Square. (See the Square integration doc for the full sync behaviour.)

Prerequisites enforced at connect: Square OAuth runs ThirdPartyIntegrationHelper::checkIntegrationPrerequisites before generating the authorization URL. For Square this enforces only one rule: no other in-house channel may already be connected (checkForInhouseChannel). Unlike Hendrickx, Vanhoutte, Shopcaisse and MplusKassa, Square does not require a branding profile at connect — checkRequiredProfiles does not list Square, so it is a no-op for Square. No payment or billing profile is required either.

Branding is recommended but not blocking. Square imports each location's social links (Facebook, X/Twitter, Instagram) into a branding profile, but does NOT supply colors, fonts, logos or slogans. Set up your branding at Settings → Brand so imported locations look right — the guided-setup checklist lists it as the first step — but the connect itself is not blocked if you skip it.

One in-house POS per merchant. Square is an in-house, merchant-scoped channel and is mutually exclusive with any other in-house channel. If the merchant already has another in-house channel (Hendrickx, Vanhoutte, Shopcaisse, MplusKassa, or Lightspeed K-Series) connected, the OAuth-initiate request is rejected with 400 "Another in-house channel is already enabled for this merchant".


Integration Type

  • Scope: Merchant-scoped (one OAuth connection covers all locations)
  • Payment Processing: Included — Square handles kiosk payments (Square Terminal) and online payments (Square Web Payments SDK)
  • Branding Profile: Recommended, NOT enforced at connect (Square supplies only social links, not colors/fonts/logo)
  • Payment Profile: NOT required (Square processes all customer payments)
  • Billing Profile: Optional (Upvendo platform billing only; the connect dialog warns if no default billing profile exists)
  • Menu Sync: Per-entity automatic push/pull/skip (categories, modifier groups, items, variant groups)
  • Location Import: Automatic — Square locations are imported with rich data (address, business hours, coordinates, timezone, currency, language, phone, email, social links)
  • Inventory Sync: Per-entity automatic (stock counts per item variation per location)

The KB previously stated branding is required for Square and that the connect uses a SquareConnectDialog. Both are wrong: checkRequiredProfiles does not require any profile for Square, and the connect dialog is SquareChannelDialog.vue (in-house channel dialog) / SquareForm.vue (Settings tab). See the corrected integration doc.


Discovery Flow (JSON - for deterministic parsing)

The discovery questions and the dynamic step list are produced by Emily (the proxy/back-end onboarding service), not hard-coded in the back office. The JSON below mirrors the standard channel branching Emily uses; the back office only sends the inferred channel (Kiosk only / Online ordering only / Both) to Emily's setup-steps endpoint and falls back to the built-in BASE_SETUP_STEPS.square checklist when Emily returns nothing.

discovery_questions
[
  {
    "id": "channels",
    "text": "What channels do you want to set up with Square?",
    "text_i18n": {
      "nl": "Welke kanalen wil je instellen met Square?",
      "fr": "Quels canaux souhaitez-vous configurer avec Square ?",
      "de": "Welche Kanäle möchten Sie mit Square einrichten?",
      "es": "¿Qué canales quieres configurar con Square?",
      "it": "Quali canali vuoi configurare con Square?",
      "pt": "Que canais quer configurar com o Square?"
    },
    "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 Square?",
    "text_i18n": {
      "nl": "Heb je je menu al ingesteld in Square?",
      "fr": "Votre menu est-il déjà configuré dans Square ?",
      "de": "Ist Ihr Menü bereits in Square eingerichtet?",
      "es": "¿Ya tienes tu menú configurado en Square?",
      "it": "Hai già configurato il tuo menu in Square?",
      "pt": "Já tem o seu menu configurado no Square?"
    },
    "options": [
      "Yes, my menu is in Square",
      "No, I will build it in Upvendo"
    ],
    "labels": {
      "nl": {
        "Yes, my menu is in Square": "Ja, mijn menu staat in Square",
        "No, I will build it in Upvendo": "Nee, ik bouw het in Upvendo"
      },
      "fr": {
        "Yes, my menu is in Square": "Oui, mon menu est dans Square",
        "No, I will build it in Upvendo": "Non, je vais le créer dans Upvendo"
      },
      "de": {
        "Yes, my menu is in Square": "Ja, mein Menü ist in Square",
        "No, I will build it in Upvendo": "Nein, ich erstelle es in Upvendo"
      },
      "es": {
        "Yes, my menu is in Square": "Sí, mi menú está en Square",
        "No, I will build it in Upvendo": "No, lo crearé en Upvendo"
      },
      "it": {
        "Yes, my menu is in Square": "Sì, il mio menu è in Square",
        "No, I will build it in Upvendo": "No, lo creerò in Upvendo"
      },
      "pt": {
        "Yes, my menu is in Square": "Sim, o meu menu está no Square",
        "No, I will build it in Upvendo": "Não, vou criá-lo no Upvendo"
      }
    }
  }
]
setup_steps
{
  "Kiosk only": [
    {"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "optional_for_go_live": true, "note": "Recommended, not blocking. Square imports social links but not colors/fonts/logo."},
    {"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "OAuth. Locations, menu and inventory are imported automatically after connecting."},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Verify imported location data; most fields are pre-filled from Square. Add delivery zones if needed."},
    {"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", "note": "Pair your Square Terminal: generate a pairing code in Upvendo and enter it on the terminal."},
    {"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", "optional_for_go_live": true, "note": "Recommended, not blocking. Square imports social links but not colors/fonts/logo."},
    {"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "OAuth. Locations, menu and inventory are imported automatically after connecting."},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Verify imported location data; most fields are pre-filled from Square. Add delivery zones if needed."},
    {"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", "note": "Online payments are handled by the Square Web Payments SDK — no Stripe or Viva Wallet setup needed."},
    {"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", "optional_for_go_live": true, "note": "Recommended, not blocking. Square imports social links but not colors/fonts/logo."},
    {"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "OAuth. Locations, menu and inventory are imported automatically after connecting."},
    {"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Verify imported location data; most fields are pre-filled from Square. Add delivery zones if needed."},
    {"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", "note": "Pair your Square Terminal: generate a pairing code in Upvendo and enter it on the terminal."},
    {"id": "online_settings", "title": "Set up Online Settings", "route": "/online-settings"},
    {"id": "configure_online_ordering", "title": "Set up Online Ordering", "route": "/online/online-ordering", "note": "Online payments are handled by the Square Web Payments SDK — no Stripe or Viva Wallet setup needed."},
    {"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}
  ]
}

The built-in fallback checklist (BASE_SETUP_STEPS.square) has just three base steps — Set up Store Branding (/settings/brand), Connect Square (/square), and Review Imported Locations (/settings/locations) — after which the kiosk / online-ordering channel checklists are offered. The per-channel steps above merge those base steps with the shared KIOSK_CHANNEL_STEPS / ONLINE_ORDERING_CHANNEL_STEPS. 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). (PRODUCTION_POS_PROVIDERS in types.ts is not a live provider list: it is only read by POSSelectionGrid.vue, which renders inside EmilyGuidedSetupDialog — a component imported and registered in App.vue but absent from its template and referenced nowhere else as a tag, so it never renders. The list a merchant actually sees comes from GET /back-office/merchant/reseller-providers. See Guided Setup.)


Prerequisites

Enforced by ThirdPartyIntegrationHelper::checkIntegrationPrerequisites(SQUARE, ...) when you start the OAuth flow (SquareIntegrationController::initiateOAuth):

  1. No other in-house channel (required). checkForInhouseChannel aborts with 400 "Another in-house channel is already enabled for this merchant" if any other in-house channel (Hendrickx, Vanhoutte, Shopcaisse, MplusKassa, or Lightspeed K-Series) is already connected for the merchant. Square is mutually exclusive with them.
  2. No profile is enforced. checkRequiredProfiles does not list Square, so it requires nothing — there is no branding, payment, or billing profile prerequisite for Square at connect time.

Recommended (not blocked at connect):

  • Branding Profile (/settings/brand) — Square imports social links but not colors, fonts, logos or slogans, so set up your brand before going live. Imported locations get a branding profile with Square's social links pre-filled.
  • Billing Profile (/settings/billing) — for Upvendo platform invoicing only, NOT customer payments. The Square channel connect dialog shows a warning if no default billing profile exists.

Payment Profile is NOT used with Square — Square processes all customer payments directly, so you do not configure Stripe or Viva Wallet while Square is connected.


Connect Square (OAuth)

Route: /square (back office). The page has a Status tab and a Settings tab; OAuth is started from the Settings tab.

  1. Go to Square (/square) → Settings tab
  2. Click Connect with Square (the button becomes Reconnect once connected; both run the same OAuth flow). Square can also be connected from the in-house channel dialog (SquareChannelDialog.vue), which warns if no default billing profile exists.
  3. The back office calls POST /back-office/square/oauth (initiateOAuth), which checks the prerequisites above and returns a Square authorization URL; the browser is redirected to it.
  4. Log in to Square and authorize Upvendo.
  5. Square redirects back to the callback (GET /square-callback, route name square.callback). On success you land on /square/success; on failure on /square/error?message=....
  6. The callback exchanges the code for tokens, stores them on the integration record, sets the merchant's provider_integrated to square, and automatically dispatches the first full sync (SyncSquareIntegrationJob) to import locations, menu and inventory.

OAuth scopes requested include merchant/location profile, catalog items, orders, payments, inventory, customers, and device-credential management (for Terminal pairing). The state parameter is a base64-encoded JSON payload (vendor_id + timestamp + random nonce) for CSRF protection.

The previous version of this doc referenced a SquareConnectDialog modal — no such component exists. The connect UI is the Settings-tab Connect with Square button (SquareForm.vue) and the in-house SquareChannelDialog.vue.

Configure sync settings (optional)

On the Settings tab you can set the Daily Sync Time (sync_time) — the time of day the automatic daily sync runs (menu, locations, inventory). It must be a whole or half hour (HH:00 or HH:30). The time you type is your local time, and it stays at that local time across daylight-saving changes; a caption under the field names the timezone the sync will run in. There are no per-data-type toggles and no sync-direction picker; everything syncs together and direction is decided automatically per entity. You can also trigger a manual full sync anytime with Sync Now on the Status tab.


Locations Imported Automatically

Once connected, the location sync imports all Square locations as Upvendo locations. For each imported location, Upvendo:

  • Imports location data — name, full address, coordinates, business hours, timezone, country, currency, language, phone, email, description, status
  • Imports social links — Facebook, X/Twitter, Instagram → saved to the location's branding profile
  • Creates a branding profile per location (defaults + Square social links)
  • Links billing using the default billing profile's Stripe customer ID, if one exists

After import, go to Settings → Locations (/settings/locations) to:

  • Verify the imported data is correct (most fields are pre-filled)
  • Customize branding (logo, colors, fonts — Square does not provide these)
  • Add delivery zones (if doing delivery)

Unlike MplusKassa (which supplies only the branch name), Square provides rich location data, so most fields arrive pre-filled.


Payment Flow

Square processes all customer payments, so no payment profile, Stripe, or Viva Wallet configuration is needed.

Kiosk Payments (Square Terminal)

Customer Order → Upvendo Kiosk → Create Square Order → Terminal Checkout → Payment on Terminal → payment.updated webhook → payment captured

A Square Terminal must be paired to the kiosk device first (see below). The kiosk creates a Square order and a Terminal Checkout on the paired device, and payment is confirmed via the payment.updated webhook.

Online Ordering Payments (Square Web Payments SDK)

Customer Order → Online Ordering → Square Web Payments SDK (card tokenization) → POST /online-ordering/payment/process → payment.updated webhook → payment verified

The storefront tokenizes the card with Square's Web Payments SDK; the back office processes the payment via OnlineOrderingController::processSquarePayment. Square replaces Stripe for online payments — no Stripe or Viva Wallet setup is needed.


Pair a Square Terminal (Kiosk)

For kiosk in-person payments, pair a Square Terminal to the device:

  1. Go to Device Management → Devices (/device-management/devices)
  2. Open the device and its Reader / Terminal section — for a Square-integrated merchant this shows the Pair Square Terminal dialog
  3. Click Generate Pairing Code (the button becomes Regenerate Code if a code already exists). This calls POST /devices/{id}/square-device-code.
  4. Enter the displayed code on the Square Terminal before its "Valid until" time — the expiry is set by Square (the pair_by timestamp returned by the API), not a fixed window
  5. Once paired, the device.code.paired webhook confirms the connection: Upvendo records the terminal device ID, marks the device code is_pending = false / is_connected = true, and stamps paired_at

The terminal must be signed into the same Square account that is connected to Upvendo.


Disconnecting Square

To disconnect Square:

  1. Go to Square (/square) → Settings tab
  2. Click Delete and confirm
  3. This revokes the Square token, deletes the integration record, and clears the merchant's provider_integrated

Warning: Disconnecting Square means:

  • No more payment processing (kiosk and online) until an alternative is configured — set up Stripe (online) and/or Viva Wallet (kiosk) before disconnecting
  • Sync stops

Disconnect is refused if a sync is currently in progress.


Troubleshooting

"Another in-house channel is already enabled" when connecting

Square is mutually exclusive with every other in-house channel (Hendrickx, Vanhoutte, Shopcaisse, MplusKassa, Lightspeed K-Series). Remove the other in-house integration before connecting Square.

"OAuth connection failed"

  • Verify the merchant has an active Square account in good standing
  • Ensure you have owner/admin permissions on the Square account
  • Try Connect with Square again from the Square page (/square) → Settings tab

"Locations not appearing after connecting"

  • The first full sync runs automatically after OAuth — check the sync status on the Square page → Status tab
  • If sync looks stuck, click Sync Now on the Status tab
  • Review the sync logs for errors
  • Trigger a manual sync: Square page → Status tab → Sync Now
  • Sync is per-entity automatic — items created in either system sync per the direction algorithm
  • Check the Daily Sync Time on the Settings tab (it is your local time — the caption under the field names the timezone it runs in)

"Square Terminal not connecting"

  • Ensure the terminal is powered on and connected to the internet
  • Generate a new pairing code in Upvendo (Device Management → device → Pair Square Terminal) if the previous code passed its "Valid until" time
  • The terminal must be signed into the same Square account connected to Upvendo
  • Pairing is confirmed by the device.code.paired webhook — the device should show a pending device code before pairing

"Online payments not working"

  • Verify the location is synced with Square (it should have a Square location ID in location settings)
  • Ensure the Square account has payment processing enabled
  • Check the Square Dashboard for account holds or verification requirements