Admin, billing & security
The control room for your whole workspace — locations, users, modules, billing, branding, operational settings, and the audit/security layer that keeps it all accountable.
🏢 Tenant admin
A quick-action grid jumps you to everything that configures how FurnFlow runs:
Your physical locations / showrooms.
Stock locations that can serve one, several or all branches.
Invite people, set role, branch and access.
Groups & per-user grants — see Roles.
Turn modules on/off and start 14-day trials.
Issue API keys and subscribe to events.
Warehouses serve branches. This mapping is what lets FurnFlow know which stock can fulfil which branch's orders — set it up before you expect availability and transfers to behave.
💳 Billing, caps & trials
- Live seats / branches / warehouses usage with at-capacity warnings; going over a cap is blocked until you upgrade.
- Monthly spend history — click a month for a per-line breakdown.
- Upgrade/downgrade your plan (it syncs your modules & caps); manage invoices, payments and dunning in the billing portal.
- Trial higher-tier modules free for 14 days, then upgrade to keep them.
| Plan | Users | Branches | Warehouses |
|---|---|---|---|
| Starter | 5 | 1 | 1 |
| Core | 25 | 3 | 2 |
| Operations | 75 | 8 | 3 |
| Enterprise | Unlimited | Unlimited | Unlimited |
Card-free trial flow (default)
New tenants sign up without a credit card and get a 14-day free trial. The card is captured later — either by the operator before the trial ends or via a self-serve "Add payment method" CTA. Three things track this transition:
| Field | Where | Meaning |
|---|---|---|
tenant.has_card_on_file | db.tenants | true once a paymentMethod is attached + confirmed (signup or in-app or first successful charge) |
tenant.requires_card_by | db.tenants | same value as trial_ends_at until a card is added; cleared to null thereafter |
tenant.lifecycle_status | db.tenants | "trial" → "active" (card added or trial converted) → "past_due" (failed invoice) → "suspended" (canceled) |
Operator-facing: Global Admin → Licensing shows tier counts so you can spot at-risk trials. Tenant-facing: the Tenant admin → Overview shows a colour-coded countdown banner (🎁 blue → ⏰ amber → ⚠ red → ⛔ red) escalating as the trial winds down, plus a one-click "💳 Add payment method" CTA that opens an inline Stripe Elements modal.
Trial reminder emails (5-stage cadence)
Best-effort reminders fire at five stages relative to trial_ends_at. Each stage debounces via tenant_events so each tenant gets each stage at most once:
| Stage | Trigger | Tone |
|---|---|---|
| T-7 | 7 days before expiry | Gentle heads-up |
| T-3 | 3 days before | Warmer — "to keep things running" |
| T-1 | 1 day before | Urgent — "last reminder" |
| T+0 | Day of expiry | "Workspace is read-only until you add a card" |
| T+1 | 1 day past expiry | Final notice — "Reactivate now" |
Triggered opportunistically on every GET /api/billing/overview read (so tenants who open Billing see them) and via POST /api/admin/run-trial-reminders for hands-off operation (wire to Azure Functions / GitHub Actions cron once a day).
Read-only mode on trial expiry without card
If a tenant's trial ends without a card on file, the enforceTrialActive middleware returns 402 trial_expired_no_card on write endpoints (POST / PUT / PATCH / DELETE). Reads stay open — the customer can always view their data. The SPA catches the 402 globally and shows a persistent red top banner with an "Add payment method" CTA that jumps to Billing where the Stripe Elements modal handles the recovery.
Exempt paths (must always work for self-recovery): /api/billing/add-payment-method, /api/billing/portal-session, /api/billing/overview, /api/me/*, /api/auth/*, /api/bootstrap, /api/tenant/license, /api/logout. Platform admins bypass.
Operators who prefer the legacy "card required at signup" flow can set SIGNUP_REQUIRE_CARD=true. The signup wizard then shows the 4-step flow with the Payment step, and the trial gate never fires (every tenant has a card from day 1).
🎨 Domain & branding
Everything customers see can carry your identity, not FurnFlow's:
| Setting | Effect |
|---|---|
| White-label name | The brand name shown to customers. |
| Logo & favicon | Your marks across the portal & emails. |
| Primary & accent colours | Theme the customer experience — with a live preview. |
| Custom domains | Serve the portal on your own domain. |
| Portal welcome & tracking | The customer-portal greeting & delivery-tracking copy. |
| "Powered by" toggle | Show or hide the FurnFlow attribution. |
| Support, legal & social links | Contact details and footer links customers see. |
⚙️ Settings
Operational configuration — the rules the whole app follows. Sections start collapsed; expand only what you need.
Currency (defaults to CAD for Canada), tax rates and tax display.
Base + per-km + per-item fee engine.
Default windows & lead times.
Validity windows, deposit %, payment terms.
What customers get notified about.
Changing a discount cap here instantly changes who needs approval in Quotes and Orders — Settings is the policy layer the operational screens obey.
🛡 Audit, security & access
FurnFlow is multi-tenant and audited by design, so your data stays yours and every sensitive change is traceable.
Money, permission & status changes record who, what, before/after and when.
Email + password (securely hashed) issues a session token; integrations can use an X-API-Key.
Every record carries a tenant id; other companies' data is invisible.
Field roles see their branch; owners/admins/executives/finance see across all.
That's the whole platform. Need a hand with something not covered here? In the app, use Send feedback in the account menu — it reaches the FurnFlow team and is tracked. Open the app →