Skip to main content

FlowPOS Architecture: Multi-Tenancy, Signup Automation & Tax Adapter Pattern

This document describes how FlowPOS implements multi-tenancy, automated tenant provisioning on signup, and the Tax Adapter pattern for global electronic invoicing scalability.


1. Multi-Tenancy Architecture

Strategy: Row-Level Isolation with business_id

FlowPOS uses shared database, shared schema with row-level isolation via a business_id column on every tenant-scoped table. This is the most common and maintainable approach for SaaS applications.

Key Points

  • Single database instance for all tenants (dev, staging, production each have one DB)
  • No separate DB per client — avoids operational overhead
  • No schema-per-tenant — all businesses share the same schema
  • Tenant identifier: business_id (UUID) on all business-scoped tables

Database Schema

All tenant-scoped tables include business_id:

user                    (global - no business_id)
business (tenant root - no business_id on self)
business_user (links user ↔ business)
document_counter (business_id)
product (business_id)
sale (business_id)
customer (business_id)
location (business_id)
address (business_id)
inventory_* (business_id)
credit_note (business_id)
debit_note (business_id)
fel_cancellation (business_id)
accounts_payable_* (business_id)
accounts_receivable_* (business_id)
... and 50+ more tables

Tables Without business_id

  • user — global identity
  • country, currency — reference data
  • System tables: module, parameter_catalog, communication_template (with business_id: null for system-wide templates)
  • Join tables that derive context from parent (e.g., sale_detail via sale.business_id)

Application-Layer Enforcement

  • Backend: Every repository/service filters by business_id from the authenticated user's context
  • Guards: RolesGuard and CASL policies enforce business_id via role_by_business_id in Firebase custom claims
  • Frontend: API calls include businessId from user context (selected business)

Row-Level Security (RLS) — Future Enhancement

PostgreSQL RLS is not yet enabled in migrations. The Metabase docs (docs/metabase/) describe an RLS-based strategy for defense-in-depth:

  • Create one PostgreSQL role per business
  • Enable RLS on tenant tables
  • Use current_setting('app.current_business')::uuid in policies
  • Metabase would connect with per-tenant DB roles for embedded dashboards

Current state: Application-layer enforcement only. RLS can be added gradually for additional security without changing the application logic.


2. Signup & Tenant Provisioning Automation

Flow: No Separate DB Per Client

When a user signs up, the system does not spin up a new database instance. Instead, it:

  1. Creates a row in the user table
  2. Guides the user through onboarding
  3. Creates a business row and links it via business_user
  4. Updates Firebase custom claims with role_by_business_id

All of this happens within the existing shared database.

Signup Flow (Step by Step)

  1. Firebase Auth: User signs up with email/password or Google (createUserWithEmailAndPassword, signInWithRedirect)
  2. Backend createUser or getOrCreateUser:
    • Creates user record with firebase_oauth_uid, email, full_name
    • Updates Firebase custom claims with db_user_id
  3. Frontend redirects to onboarding (e.g., /onboarding in PWA)
  4. Onboarding Orchestrator (OnboardingOrchestratorService):
    • Step 1 — Business: upsertOnboardingBusiness creates business, business_user (Owner), updates Firebase claims with role_by_business_id
    • Step 2 — Location: upsertUserLocation creates location, address, updates onboarding_status
    • Step 3 — Billing: patchOnboardingBill updates business with FEL credentials, legal name, tax ID; sets oboarding_completed: true in Firebase claims

Key Files

ComponentPath
Onboarding orchestratorapps/backend/src/users/application/onboarding-orchestrator.service.ts
Users service (createUser)apps/backend/src/users/application/users.service.ts
Onboarding PWA pageapps/frontend-pwa/src/pages/OnboardingPage.tsx
Onboarding formsapps/frontend-pwa/src/components/onboarding/

Design Decisions

  • Single DB: Easier ops, backups, migrations
  • Onboarding as a flow: User completes business, location, billing in sequence — no "empty" tenants
  • Firebase claims: role_by_business_id and db_user_id drive authorization; no extra session storage

3. Tax Adapter Pattern (Global Scalability)

Purpose

To support multiple countries' electronic invoicing (FEL, SAT, DIAN, TaxJar, etc.) without coupling business logic to a single certifier. The adapter pattern allows adding new regions by implementing an interface and registering the adapter.

Interface: ElectronicCertificationProvider

// apps/backend/src/fel/domain/electronic-certification-provider.interface.ts

export interface ElectronicCertificationProvider {
certifyDocument({
taxId,
xmlContent,
apiUrl,
token,
}: IProviderCertifyDocumentParameters): Promise<undefined>;

getSharedInfo({
taxId,
data1,
data2,
username,
apiUrl,
token,
}: IProviderGetSharedInfoParameters): Promise<unknown>;
}

Current Implementations (Guatemala — FEL)

AdapterFileCertifier
Digifactprovider-digifact.service.tsDigifact (Guatemala)
Infileprovider-infile.service.tsInfile (Guatemala)
RPAfelApiprovider-rpafelapi.service.tsRPAfelApi intermediary (Guatemala)

Routing

  • ProviderService maintains a providerMap keyed by providerName
  • Document carries felProviderData.providerName (e.g., "digifact", "infile", "rpafelapi")
  • FelService.certifyDocument gets provider via this.providerService.getProvider(felProviderData.providerName)
  • RPAfelApi path is also controlled by env USE_RPA_FEL_API=true for the intermediary API

Strategy for Going Global

Do not build adapters for Mexico, Colombia, USA, etc. yet. Build the interface and keep Guatemala as the beachhead. When traction comes in another country:

  1. Implement ElectronicCertificationProvider for that country (e.g., MexicoSATAdapter, ColombiaDIANAdapter, USATaxJarAdapter)
  2. Register it in ProviderService.providerMap
  3. Add country/provider selection in business settings and document flow
  4. Optionally hire a contractor for the module — the interface keeps it isolated

Document Model (Invoice / FelDocument)

The Invoice interface in fel.interface.ts is Guatemala/FEL-oriented (e.g., establishmentCode, vatAffiliationCode, Spanish field names). For true global support:

  • Consider a canonical internal model + country-specific mappers (FEL, SAT, DIAN, etc.)
  • The ElectronicCertificationProvider could evolve to accept a canonical document; each adapter would map to the local format (XML, JSON, etc.)

4. Summary Table

TopicImplementationLocation
Multi-tenancybusiness_id on all tenant tables, single DBMigrations: packages/backend/database/src/migrations/
RLSNot implemented; documented as future optiondocs/metabase/info.md
Signup automationOnboarding flow creates business, location, billing in shared DBOnboardingOrchestratorService
Tax adapter interfaceElectronicCertificationProviderfel/domain/electronic-certification-provider.interface.ts
Tax adapter implementationsDigifact, Infile, RPAfelApi (Guatemala)fel/infrastructure/provider-*.service.ts
Provider routingProviderService + providerMapfel/application/provider.service.ts

5. Apps Overview (Backend & Frontend PWA)

Backend (apps/backend)

  • Framework: NestJS 11
  • Structure: Domain-driven, one folder per bounded context (e.g., sales/, fel/, businesses/)
  • Layers: interfaces/ (controllers, DTOs), application/ (services), domain/ (interfaces, types), infrastructure/ (repositories)
  • Notable modules: FEL, sales, businesses, onboarding (users), cash-register, accounts-payable, accounts-receivable, communications, metabase
  • Auth: Firebase Admin; RolesGuard + CASL for business_id-scoped authorization
  • Database: Kysely ORM, migrations in packages/backend/database

Frontend PWA (apps/frontend-pwa)

  • Stack: React, Vite, PWA
  • Structure: pages/, components/, services/, hooks/, contexts/, i18n/
  • Auth: Firebase Auth (email/password, Google); token in requests
  • Onboarding: OnboardingPage with step-based flow (business → location → billing)
  • Services: One service per backend domain (e.g., salesService, felService, onboardingService)

  • CURSOR.md — Project context and patterns
  • docs/metabase/info.md — RLS strategy for Metabase
  • docs/fel/ — FEL architecture, RPAfelApi integration, field mappings