Appearance
Square Onboarding (Reseller)
Overview
How a reseller (partner) onboards a client merchant who uses Square as their POS. There are two distinct phases:
- Reseller-side (partner context): the reseller creates a client merchant from the Clients page and sends login credentials. This is reseller-specific surface that exists in code.
- Merchant-side (client context): the Square connection itself is the standard merchant Square flow — there is no reseller-specific Square route. The reseller switches into the client's merchant context ("client mode", backed by the
admin-vendor-overridemiddleware) and runs the normal/squareOAuth connect, or the merchant does it themselves after logging in.
Key facts (code-verified):
- A reseller is a first-class entity (
Resellermodel,resellerscollection). Reseller users hold thereseller-owner/reseller-administrator/reseller-reproles; thereseller-administratordisplay name is "Partner Administrator". A reseller's merchants are called clients. - Client creation does NOT pick a POS provider.
ResellerService::createMerchantnever references Square. POS selection happens later, inside the client's merchant context. - Square availability is per-reseller. A reseller can offer Square to a client only if
squareis in that reseller'spos_providersarray. Emily filters the POS options to the reseller's assigned providers in client mode. - No Payment Profile setup — when Square is connected, Square handles kiosk (Square Terminal) and online (Square Web Payments SDK) payments. Reseller-created merchants are forced to
stripe_onboarding_completed = trueat creation. - No prerequisite profile is enforced for Square — unlike the other POS providers, Square is NOT in the branding-required list (
ThirdPartyIntegrationHelper::checkRequiredProfiles), so the Square connect path enforces no profile at all. Branding is still recommended because Square does not provide colors, fonts, or logos (only social links are imported). - Locations, menu, and inventory sync automatically on first connect — no separate sync step.
See the standard merchant docs for the shared integration mechanics: Square Integration Setup, Square Integration feature, Guided Setup, Payment Profiles.
Reseller-Specific Surface (verified in code)
Creating the client merchant
A Partner Admin (a reseller-administrator / reseller-owner user, or a global admin acting for the reseller) creates a client merchant:
- Go to Clients (
/clients) - Click Add to open the New Client dialog (
ClientFormDialog.vue) - Fill in: First name, Last name, Email, Password, optional Phone, optional Business name, Country/locale, Default language. (There is no POS-provider or owner-name field here.)
- Save — this POSTs to
POST /back-office/partner/merchants(ResellerController::createMerchant)
What ResellerService::createMerchant sets on the new merchant:
reseller_id= the partner's reselleronboarding_type=resellermerchant_status=Newstripe_onboarding_completed=true(forced)- the owner user's
must_change_password=true - appends the new merchant to the partner user's
vendor_ids
It calls the shared MerchantService::create and runs the Cloudflare D1 initialize_merchant_tables migration. The request is authorized by PermissionService::isGlobalUser (true for reseller-owner / reseller-administrator / global-administrator).
Sending credentials
From the client detail page (/clients/:id, route name clients-id), Send Credentials issues a temporary password to the merchant owner: POST /back-office/partner/merchants/{merchantId}/send-credentials (ResellerController::sendMerchantCredentials). A reseller can only send credentials for a merchant whose reseller_id matches their own.
Which POS providers the client can choose
The reseller's pos_providers array (on the Reseller model) controls the POS options surfaced to its clients. ResellerService::getMerchantResellerProviders filters config('pos-providers.php') down to the reseller's assigned keys (dropping inactive / test-only providers in production). Square is offered to the client only if square is in the reseller's pos_providers. Square is a defined provider in config/pos-providers.php (key: square, countries US/CA/GB/AU/JP/IE/FR/ES) but is not auto-included for resellers — it must be assigned per reseller (managed by a global admin on the reseller record).
Onboarding the client via Emily ("client mode")
When the partner operates in a specific client's context, Emily runs in client mode (store.getters.getMerchantMode; the Emily endpoint resolves to emily-merchant). This is the same full merchant onboarding flow, scoped to the client merchant. In client mode the POS-selection screen always shows (even for a single provider) and is filtered to the reseller's pos_providers.
Not verified here: Emily/proxy onboarding-state internals, greeting copy, and the partner-dashboard "open onboarding" event live in the SPA/proxy and were only inspected at the component level.
Connecting Square (same as a standalone merchant)
There is no reseller-specific Square connect route. Whether the reseller switches into the client's context (client mode / admin-vendor-override) or the merchant logs in directly, the connect is the standard flow:
- Backoffice page Square (
/square) → Connect with Square - Frontend dispatch →
POST /back-office/square/oauth(SquareIntegrationController::initiateOAuth) - On success, redirect to
/square/successand a full sync (locations, menu, inventory) starts automatically
Discovery Flow (JSON - for deterministic parsing)
discovery_questions
[
{
"id": "channels",
"text": "Which channels for this client merchant?",
"text_i18n": {
"nl": "Welke kanalen voor deze klant?",
"fr": "Quels canaux pour ce marchand client ?",
"de": "Welche Kanäle für diesen Kunden?",
"es": "¿Qué canales para este comercio cliente?",
"it": "Quali canali per questo commerciante cliente?",
"pt": "Que canais para este comerciante cliente?"
},
"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 Square?",
"text_i18n": {
"nl": "Heeft de handelaar het menu ingesteld in Square?",
"fr": "Le marchand a-t-il configuré son menu dans Square ?",
"de": "Hat der Händler sein Menü in Square eingerichtet?",
"es": "¿El comercio tiene su menú configurado en Square?",
"it": "Il commerciante ha configurato il proprio menu in Square?",
"pt": "O comerciante tem o menu configurado no Square?"
},
"options": [
"Yes, menu is in Square",
"No, will create in Upvendo"
],
"labels": {
"nl": {
"Yes, menu is in Square": "Ja, menu staat in Square",
"No, will create in Upvendo": "Nee, wordt aangemaakt in Upvendo"
},
"fr": {
"Yes, menu is in Square": "Oui, le menu est dans Square",
"No, will create in Upvendo": "Non, sera créé dans Upvendo"
},
"de": {
"Yes, menu is in Square": "Ja, das Menü ist in Square",
"No, will create in Upvendo": "Nein, wird in Upvendo erstellt"
},
"es": {
"Yes, menu is in Square": "Sí, el menú está en Square",
"No, will create in Upvendo": "No, se creará en Upvendo"
},
"it": {
"Yes, menu is in Square": "Sì, il menu è in Square",
"No, will create in Upvendo": "No, sarà creato in Upvendo"
},
"pt": {
"Yes, menu is in Square": "Sim, o menu está no Square",
"No, will create in Upvendo": "Não, será criado no Upvendo"
}
}
}
]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", "optional_for_go_live": true, "note": "Recommended, not enforced for Square. Square imports social links but not colors/fonts/logo."},
{"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "Standard merchant Square OAuth (POST /back-office/square/oauth). Locations, menu, and inventory import automatically. Available only if the reseller's pos_providers includes 'square'."},
{"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Most fields are pre-filled from Square. Verify details and add delivery zones if needed."},
{"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 and Pair Square Terminal", "route": "/device-management/devices", "note": "Generate a device code in Upvendo and enter it on the Square Terminal; the device.code.paired webhook confirms pairing."},
{"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "optional_for_go_live": true, "note": "Optional. Upvendo platform invoicing only; not customer payments (Square handles those). Reseller billing/commissions are handled separately."}
],
"Online ordering only": [
{"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "optional_for_go_live": true, "note": "Recommended, not enforced for Square. Square imports social links but not colors/fonts/logo."},
{"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "Standard merchant Square OAuth (POST /back-office/square/oauth). Locations, menu, and inventory import automatically. Available only if the reseller's pos_providers includes 'square'."},
{"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Most fields are pre-filled from Square. Verify details and add delivery zones if needed."},
{"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 use the Square Web Payments SDK — no Stripe or Viva Wallet setup needed."},
{"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "optional_for_go_live": true, "note": "Optional. Upvendo platform invoicing only; not customer payments."}
],
"Both": [
{"id": "branding_profile", "title": "Set up Store Branding", "route": "/settings/brand", "optional_for_go_live": true, "note": "Recommended, not enforced for Square. Square imports social links but not colors/fonts/logo."},
{"id": "connect_pos", "title": "Connect Square", "route": "/square", "channel": "square", "note": "Standard merchant Square OAuth (POST /back-office/square/oauth). Locations, menu, and inventory import automatically. Available only if the reseller's pos_providers includes 'square'."},
{"id": "complete_location", "title": "Review Imported Locations", "route": "/settings/locations", "note": "Most fields are pre-filled from Square. Verify details and add delivery zones if needed."},
{"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 and Pair Square Terminal", "route": "/device-management/devices", "note": "Generate a device code in Upvendo and enter it on the Square Terminal; the device.code.paired webhook confirms pairing."},
{"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 use the Square Web Payments SDK — no Stripe or Viva Wallet setup needed."},
{"id": "billing_profile", "title": "Set up Billing Profile", "route": "/settings/billing", "optional_for_go_live": true, "note": "Optional. Upvendo platform invoicing only; not customer payments."}
]
}Notes on Verification
- Reseller mechanics (
Resellermodel, roles,ResellerService::createMerchant,ResellerController,pos_providers,/clientspages, Emily client mode) are confirmed inupvendo-backendandupvendo-backoffice. - Square mechanics (OAuth scopes,
provider_integrated = square,isSquareIntegrated()payment override, no Square prerequisite profile, location/menu/inventory sync, webhooks) are confirmed and detailed in shared/integrations/square.md. - Route corrections applied: the online-ordering route is
/online/online-ordering(not/pickup-and-delivery/online-ordering, which does not exist);/online-settingsis a real precursor step. A/orders"Go Live" route does not exist in the backoffice and has been removed. - Reseller billing / commissions / payout settings (platform-collects vs reseller-collects, Stripe Connect, commission ledger, payout statements) exist in code but are a separate subsystem from POS onboarding and are out of scope for this Square onboarding doc.
- Not verified here: Square Web Payments SDK storefront/proxy tokenization, Emily/proxy onboarding-state internals, and email-delivery of credentials live outside these two repos.