Back to Projects
Live

XFinance

Multi-Tenant Finance & Accounting SaaS

~/xfinance

$ deploy --project xfinance

HTTP/2 200 OK

SAAS

Key Technical Highlights

Dual deployment architecture — DEPLOYMENT_MODE=saas enables full multi-tenancy with subdomain routing and super admin; standalone mode bypasses subscription gates for single-client on-premise deployments

All data scoped by groupId on every Prisma model, sourced from request context — never re-queried from the database mid-request, keeping tenant isolation fast and auditable

Real-time sidebar updates via Socket.IO — balance sheets, payroll summaries, and audit logs update instantly across all connected clients

CI/CD runs parallel Docker image builds to GHCR on every push to main; standalone clients self-update via Watchtower watching a :stable tag — zero manual intervention for client upgrades

Per-group branding — each tenant configures their own logo and colour theme, served from their subdomain

Root-caused and repaired a live double-entry ledger corruption with a purpose-built, replay-based CLI tool rather than a manual database edit — verified via full transaction-by-transaction reconciliation

The Case Study

The Problem

Nigerian businesses managing multiple entities — subsidiaries, departments, branch offices — had no affordable, locally-built accounting SaaS that understood their operating context. Enterprise tools were too expensive and foreign. Local tools were too simple.

The Solution

XFinance is a multi-tenant Finance and Accounting SaaS with a dual deployment model. Businesses use the hosted SaaS at xfinance.ng with full multi-tenancy, or deploy the standalone Docker image for a single-client on-premise installation. Same codebase. Different runtime mode.

The Architecture Decision That Defines It

The dual deployment mode was the core architectural challenge. A single environment variable — DEPLOYMENT_MODE — switches the entire application context. In SaaS mode, subdomain routing identifies the tenant group, the super admin panel is active, and subscription gating controls feature access. In standalone mode, a fixed DEFAULT_GROUP_ID is injected at startup. No subscription checks. No subdomain routing. No super admin overhead.

This means enterprise clients get a fully white-labelled, self-hosted installation while the SaaS product evolves from the same repository.

Data Scoping

Every Prisma model carries a groupId. The request context sets it once on authentication — it is never re-queried from the database mid-request. Tenant isolation is enforced at the ORM layer, making it both fast and auditable. Any query missing a groupId scope would be caught immediately in review.

Real-Time Updates

Finance dashboards need to reflect live data. Socket.IO powers a real-time sidebar that updates automatically when a payroll run completes, a bank reconciliation is confirmed, or inventory levels change. No polling. No manual refresh.

CI/CD

GitHub Actions builds Docker images in parallel and pushes to GHCR on every push to main. The SaaS instance auto-deploys from the :latest tag. Standalone clients run Watchtower, which monitors the :stable tag and self-updates when a new image is promoted — zero manual intervention required for client upgrades.

Key Features

  • General Ledger with double-entry bookkeeping
  • Bank reconciliation module
  • Payroll with deduction management
  • Store inventory tracking
  • Full audit logging with change history
  • Per-tenant logo and colour theme branding
  • Subscription management (SaaS mode only)
  • Self-update via Watchtower (standalone mode)

A Production Ledger Correction, Done Right

A double-entry ledger corruption bug reached live production: an opening-balance code path wrote the cached account balance directly and posted a journal entry for the same amount, double-counting every opening balance against the cached figure. Rather than hand-editing the affected balance in the database, the fix was a purpose-built, dependency-free CLI repair tool that treats the transaction ledger as the sole source of truth — a read-only diagnose command shows the exact mismatch and a full transaction-by-transaction replay before anything is written, and a separate --confirm flag is required to actually reconstruct both the running balance and the final balance from scratch. The fix ran against production with a disciplined, backup-first procedure — full dump, deploy the code fix, read-only diagnosis, repair, re-diagnose to confirm agreement — followed by a manual regression test plan checking individual ledger rows at each step, not just the final total.