Merchant Onboarding
Merchant onboarding is implemented as a multi-step process spanning auth, admin approval, tenant provisioning, and profile completion.
Phase 1: Signup Intent
In apps/web/src/pages/LoginPage.tsx, email sign-in captures requested_role through Supabase OTP metadata:
consumerclient(merchant/vendor)admin(domain-constrained to@doshidhruv.com)
Phone sign-in defaults to consumer flow.
Phase 2: First API Profile Load
After successful session creation, frontend calls GET /api/v1/auth/me.
authProfileMiddleware and profile builder return:
- role and status
canUseAppneedsMerchantOnboarding- optional
code(PENDING_APPROVAL,SUSPENDED,ADMIN_EMAIL_REQUIRED)
For merchant roles, pending_approval blocks app usage and routes to pending state screens.
Phase 3: Admin Approval
Admin approves merchants via:
PATCH /api/v1/admin/users/:userId/approve
Approval behavior in apps/api/src/routes/admin.ts:
- Set user status to
active - Write audit log
- If role is
client, ensure workspace exists usingensureClientHasMerchantWorkspace(...) - Auto-generates tenant name from email (e.g.,
john@example.com->John — SalesArck) - Optionally write provisioning audit log when tenant is newly created
- Return approved user and optional workspace info
Phase 4: Merchant Workspace Bootstrap
For approved merchants without membership (legacy edge case), frontend onboarding calls:
POST /api/v1/client/bootstrap-merchant-workspace
Implemented by apps/api/src/routes/client.ts and apps/api/src/lib/merchant-workspace.ts.
Behavior is idempotent:
- If membership exists -> return existing tenant id
- If not -> create tenant + tenant_users link
- Returns:
{ tenantId, created: boolean }
Phase 5: Onboarding Wizard
Primary UI entry is apps/web/src/pages/OnboardingPage.tsx, which hosts MerchantProfileWizard.
Wizard persists onboarding data through:
PATCH /api/v1/client/tenants/:tenantId/onboarding
Patch payload supports:
posProviderPreference(square,clover,other, ornull)businessNamecountry(ISO 3166-1 alpha-2)profileobject:taxIdaddressLinecitysquareLocationHintcloverMerchantRefotherPosNotes
Server merge behavior:
- Existing JSON profile is merged with provided fields
- Undefined fields are not forcibly erased
updatedAtis refreshed
Route Gating and UX Enforcement
apps/web/src/App.tsx enforces client onboarding progression:
- If
role=clientandneedsMerchantOnboarding=true, redirect to/onboarding - Navigation hides client features until onboarding is complete
- Merchants remain authenticated but constrained to setup path
POS Preference Enforcement
After onboarding, PosSettingsPage.tsx enforces POS preference:
- Square connect button disabled if merchant chose
cloverorother - Clover connect card only visible if merchant chose
clover - Informational message explains the constraint with link back to onboarding to change
Data Model Fields Used
Merchant onboarding state is persisted in tenants:
pos_provider_preferenceonboarding_profile(JSONB)updated_at
These were added incrementally by migration 0001_lovely_patch.sql.
Operational Notes
- Approval is an explicit admin action; no auto-approval path is present.
- Tenant provisioning and onboarding are separate concerns.
- Workspace provisioning is automatic on admin approval (since
ensureClientHasMerchantWorkspaceruns during approve). - Onboarding completion currently checks POS preference presence; deeper completion checks can be extended later.