Skip to main content

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:

  • consumer
  • client (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
  • canUseApp
  • needsMerchantOnboarding
  • 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:

  1. Set user status to active
  2. Write audit log
  3. If role is client, ensure workspace exists using ensureClientHasMerchantWorkspace(...)
  4. Auto-generates tenant name from email (e.g., john@example.com -> John — SalesArck)
  5. Optionally write provisioning audit log when tenant is newly created
  6. 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, or null)
  • businessName
  • country (ISO 3166-1 alpha-2)
  • profile object:
    • taxId
    • addressLine
    • city
    • squareLocationHint
    • cloverMerchantRef
    • otherPosNotes

Server merge behavior:

  • Existing JSON profile is merged with provided fields
  • Undefined fields are not forcibly erased
  • updatedAt is refreshed

Route Gating and UX Enforcement

apps/web/src/App.tsx enforces client onboarding progression:

  • If role=client and needsMerchantOnboarding=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 clover or other
  • 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_preference
  • onboarding_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 ensureClientHasMerchantWorkspace runs during approve).
  • Onboarding completion currently checks POS preference presence; deeper completion checks can be extended later.
Written byDhruv Doshi