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 identitycountry,currency— reference data- System tables:
module,parameter_catalog,communication_template(withbusiness_id: nullfor system-wide templates) - Join tables that derive context from parent (e.g.,
sale_detailviasale.business_id)
Application-Layer Enforcement
- Backend: Every repository/service filters by
business_idfrom the authenticated user's context - Guards:
RolesGuardand CASL policies enforcebusiness_idviarole_by_business_idin Firebase custom claims - Frontend: API calls include
businessIdfrom 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')::uuidin 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:
- Creates a row in the
usertable - Guides the user through onboarding
- Creates a
businessrow and links it viabusiness_user - Updates Firebase custom claims with
role_by_business_id
All of this happens within the existing shared database.
Signup Flow (Step by Step)
- Firebase Auth: User signs up with email/password or Google (
createUserWithEmailAndPassword,signInWithRedirect) - Backend
createUserorgetOrCreateUser:- Creates
userrecord withfirebase_oauth_uid,email,full_name - Updates Firebase custom claims with
db_user_id
- Creates
- Frontend redirects to onboarding (e.g.,
/onboardingin PWA) - Onboarding Orchestrator (
OnboardingOrchestratorService):- Step 1 — Business:
upsertOnboardingBusinesscreatesbusiness,business_user(Owner), updates Firebase claims withrole_by_business_id - Step 2 — Location:
upsertUserLocationcreateslocation,address, updatesonboarding_status - Step 3 — Billing:
patchOnboardingBillupdates business with FEL credentials, legal name, tax ID; setsoboarding_completed: truein Firebase claims
- Step 1 — Business:
Key Files
| Component | Path |
|---|---|
| Onboarding orchestrator | apps/backend/src/users/application/onboarding-orchestrator.service.ts |
| Users service (createUser) | apps/backend/src/users/application/users.service.ts |
| Onboarding PWA page | apps/frontend-pwa/src/pages/OnboardingPage.tsx |
| Onboarding forms | apps/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_idanddb_user_iddrive 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)
| Adapter | File | Certifier |
|---|---|---|
| Digifact | provider-digifact.service.ts | Digifact (Guatemala) |
| Infile | provider-infile.service.ts | Infile (Guatemala) |
| RPAfelApi | provider-rpafelapi.service.ts | RPAfelApi intermediary (Guatemala) |
Routing
ProviderServicemaintains aproviderMapkeyed byproviderName- Document carries
felProviderData.providerName(e.g.,"digifact","infile","rpafelapi") FelService.certifyDocumentgets provider viathis.providerService.getProvider(felProviderData.providerName)- RPAfelApi path is also controlled by env
USE_RPA_FEL_API=truefor 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:
- Implement
ElectronicCertificationProviderfor that country (e.g.,MexicoSATAdapter,ColombiaDIANAdapter,USATaxJarAdapter) - Register it in
ProviderService.providerMap - Add country/provider selection in business settings and document flow
- 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
ElectronicCertificationProvidercould evolve to accept a canonical document; each adapter would map to the local format (XML, JSON, etc.)
4. Summary Table
| Topic | Implementation | Location |
|---|---|---|
| Multi-tenancy | business_id on all tenant tables, single DB | Migrations: packages/backend/database/src/migrations/ |
| RLS | Not implemented; documented as future option | docs/metabase/info.md |
| Signup automation | Onboarding flow creates business, location, billing in shared DB | OnboardingOrchestratorService |
| Tax adapter interface | ElectronicCertificationProvider | fel/domain/electronic-certification-provider.interface.ts |
| Tax adapter implementations | Digifact, Infile, RPAfelApi (Guatemala) | fel/infrastructure/provider-*.service.ts |
| Provider routing | ProviderService + providerMap | fel/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 forbusiness_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:
OnboardingPagewith step-based flow (business → location → billing) - Services: One service per backend domain (e.g.,
salesService,felService,onboardingService)
6. Related Documentation
- CURSOR.md — Project context and patterns
- docs/metabase/info.md — RLS strategy for Metabase
- docs/fel/ — FEL architecture, RPAfelApi integration, field mappings