Back to all work
Case Study · 03 Multi-module fintech · Built for Africa Pre-launch · Q2 2026

VaultCore

A four-module finance platform built for the African market — Business, Family, Personal, Schools — with bank-grade approval workflows, term-based fiscal periods, and a tax engine that knows eight countries. Five custom plugins. Zero JavaScript dependencies. Roughly five weeks of build.

Stack
WordPress 6.xPHP 8 · Hostinger · Paystack · zero JS deps
Role
Solo buildArchitecture · code · UI · launch
Status
Pre-launchBuild complete · onboarding next
The brief

Africa's smartest finance platform — built like one.

The personal-finance app market is saturated with consumer apps that miss African realities entirely. Mint and YNAB don't speak Naira. Most "global" SaaS finance products quietly assume calendar-month fiscal periods, two-decimal currencies, and customers who pay with credit cards. Nigerian families, businesses, and schools live in a different operational context — terms not months, Naira at thousands per dollar, Paystack instead of Stripe, manual approvals that mirror real treasury controls, and tax regimes that vary by country across the continent.

Vault Core was built from that observation. Four distinct finance modules — Business for SMEs, Family for households, Personal for individuals, Schools for educational institutions — each with their own data model, their own UI, their own role logic, but unified under a shared subscription and authentication layer. Multi-tenant by tenant-isolation: every paying client gets their own subdomain with their own WordPress installation, side-stepping Multisite's complexity in favor of cleaner data boundaries and per-client customisation freedom.

The public site is live at vaultcore.space. All five plugins are built and tested. The system is currently in pre-launch, with onboarding flows for the first cohort of clients in active preparation.

This case study covers the engineering decisions that shaped a five-week solo build of roughly 9,600 lines of pure PHP — no React, no Vue, no Webpack, no npm.

Architecture

Five plugins. Four modules. One discipline.

Vault Core is built as five independent WordPress plugins. One core, four modules. Each module owns its own tables, admin menu, AJAX handlers, role logic, and reporting surface. They all depend on the core for shared functions and shared tables.

/01 · CORE
vaultcore-base
Auth · Paystack webhooks · signup pipeline · subscriber management · platform admin
/02 · MODULE
vaultcore-business
SME finance · 2-person approvals · 8-country tax · staff payroll
/03 · MODULE
vaultcore-family
Household budgeting · members · goals · health score
/04 · MODULE
vaultcore-personal
Personal finance · debt snowball · investments · plan-gating
/05 · MODULE
vaultcore-schools
Multi-campus · term-based fiscal periods · fee schedules · approval workflow

Tenant isolation by subdomain, not Multisite

Each paying client gets their own WordPress installation on a dedicated subdomain (client.vaultcore.space), with the relevant plugins installed manually after payment. This is a deliberate architectural choice. WordPress Multisite would have been faster to set up but expensive to maintain — every plugin update propagates to every tenant, security boundaries are network-wide rather than per-tenant, and per-client customisation requires elaborate workarounds. Separate installations sidestep all of that. Each client's data lives in its own database namespace. Plugin updates are tested and rolled out per-tenant. Customisations don't risk other tenants.

Shared seams across modules

What ties the five plugins together isn't framework code — it's a small library of helper functions exposed by vaultcore-base: vc_get_user_context() resolves the active user's role and permissions, vc_format_currency() applies symbol and locale, vc_user_has_module() gates feature access, vc_build_in() generates safe SQL placeholder lists. Every module reads from these. The seams are intentionally small and deliberately stable.

The standout

Two-person approval, enforced by the database.

Real treasury controls require dual approval — an expense submitted by one person must be approved by another before it deducts from the bank balance. Application code can attempt to enforce this with conditional checks and locks, but the moment two requests arrive concurrently, the application becomes the bottleneck and the race condition becomes a real bug.

Vault Core moves the enforcement one level down: the database itself refuses to record the same approval twice. A composite UNIQUE key on (transaction_id, transaction_type, approver_id) means any second attempt to insert the same approval — by the same user, on the same transaction — fails atomically at the storage layer. No locks. No race conditions. No application-level guard logic to maintain.

vc_biz_approvals · idempotent by design SQL
CREATE TABLE wp_vc_biz_approvals (
  approval_id      INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  transaction_id   INT UNSIGNED NOT NULL,
  transaction_type VARCHAR(20) NOT NULL,  -- 'expense' | 'transfer'
  approver_id      BIGINT UNSIGNED NOT NULL,
  approved_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

  -- the line that does the real work:
  UNIQUE KEY uniq_one_per_approver (
    transaction_id, transaction_type, approver_id
  )
);

The application layer adds one further rule: the user who initiated the transaction is barred from approving it. Two distinct human approvals are required, by two distinct users, before the balance moves. The deduction itself happens only on the second approval — no half-states, no in-flight expenses showing up on a balance sheet, no rollback choreography needed if the second approval never comes.

Application code can be naive. The database is the guard. — design principle, Vault Core approvals

This is the same engineering thesis applied across the platform. Schools approvals use the same pattern in vc_sch_approvals. Subscription idempotency in vc_subscriptions uses unique reference IDs at the row level. Webhook handlers are written to be safely re-runnable. When the database can guarantee a constraint, the application doesn't have to.

A deliberate non-decision

Charts rendered in PHP. No JavaScript.

Every dashboard in Vault Core has charts — revenue versus expenses, term-by-term school income, monthly debt repayment progress, family budget burn rates. The default modern choice is a JavaScript charting library: Chart.js, Recharts, ApexCharts, D3. Vault Core uses none of them.

Charts are constructed as SVG strings in PHP, server-rendered, embedded directly in the HTML response. No external requests. No client-side rendering delay. No version-mismatch bugs between a chart library and its peer dependency. No Chart.js fonts that fail to load on slow Nigerian mobile networks. No "chart didn't render" support tickets.

The trade-off is real — server-rendered charts can't animate or rebuild on click without a page round-trip. But for a finance dashboard where the chart represents a stable view of last month's numbers, that's a feature, not a limitation. Every page in Vault Core renders under 50KB of HTML, including its charts. Print-to-PDF works perfectly because there's no JavaScript to defer. Every chart is identical across browsers because there's no library version drift.

Schools module

Nigerian schools don't run on calendar months.

This is the kind of detail that separates software built for a market from software ported into one. Nigerian schools operate on three academic terms — not twelve months. Fee schedules align to terms. Budgets are set per term. Reports are generated term-end. Every financial workflow in a school's life cycles through these three windows, not through January-to-December.

The Schools module makes this the primary fiscal axis. The current term is auto-detected from the server date. All filters, reports, fee schedules, and budgets operate on terms by default. A user can drill into a specific term or aggregate across the academic year, but the default view always answers "what's happening in this term."

Term Months Default behaviour
Term 1 September – December New academic year start; opening fee schedule applies
Term 2 January – April Mid-year continuation; rollover from Term 1
Term 3 May – July Final term; year-end closing reports auto-generate

Multi-campus, with HQ flag

School networks operate the same way denominations do — a head office plus multiple campuses. The Schools module supports this directly: each school record carries an is_hq flag. Network-level reports aggregate across campuses. Per-campus reports filter to a single school. Fund allocations are stored per entity-group, so an HQ administrator can see total revenue across the network or drill into any single campus without query gymnastics.

Personal module

Debt Snowball that finds itself.

The Debt Snowball is a popular personal-finance method: pay minimum amounts on every debt, but throw every spare Naira at the smallest balance until it's gone, then roll that payment into the next-smallest. Most apps that implement it ask the user to identify which debt is the snowball target. Vault Core auto-identifies the smallest active balance and gold-highlights it in the UI without requiring user configuration.

Monthly interest cost is calculated per debt from each row's interest rate, displayed next to the balance. The user sees, at a glance, exactly which debt is bleeding them slowest and which is bleeding them fastest. The snowball method becomes obvious by visual emphasis, not by a user manually marking up rows.

Plan-gating from a single meta key

Personal has a free tier and a premium tier. The free tier enforces a 50-transaction-per-month ceiling. Premium unlocks the debt tracker, investment log, and net-worth calculator. All plan logic in the entire module reads from one user meta key, vc_plan. The transaction ceiling is checked before every income/expense INSERT. Premium-only features render an upgrade notice instead of throwing errors. Single source of truth, gated everywhere.

The tax engine

Eight African countries, one calculator.

The Business module includes a built-in tax calculator covering Corporate Income Tax (CIT), Value Added Tax (VAT), Withholding Tax (WHT), Pay-As-You-Earn (PAYE), and local levies — across Nigeria, Ghana, Kenya, South Africa, Rwanda, Tanzania, Uganda, and Ethiopia. Plus a custom-rates option for any country not yet hardcoded.

No third-party tax API is involved. Every rate table lives in the plugin source, editable via the admin UI when rates change. The advantages: zero external dependencies (which would be fragile across the eight countries), instant deployment when a country's rates change, no per-call API costs that would scale poorly with subscriber count, and full offline operation. The trade-off — an annual rate review by hand — is a small price for that level of independence.

For an African SME running operations across two or three countries, this single feature is sometimes the platform's primary value driver. Most "global" finance tools do US, UK, and EU tax. Vault Core knows the continent.

Payments & signup

Flat plans. Verified webhooks.

The signup flow is a four-step form: pick a module, fill details, review, pay. The temp order is created via AJAX, Paystack's popup fires with the correct amount in kobo, and on success a webhook lands at a custom REST route — POST /wp-json/vaultcore/v1/webhook — where the HMAC-SHA512 signature is verified before any database mutation occurs.

Pricing by module

Module Plan Price
BusinessSingle flat plan₦10,000 / month
FamilySingle flat plan₦10,000 / month
PersonalFree + Premium₦5,000 / month
SchoolsSingle flat plan₦30,000 / month

Webhooks handled: charge.success, subscription.create, subscription.disable, invoice.payment_failed, invoice.update. Each handler is idempotent by design — replaying a webhook produces the same final state, never a duplicate row or a phantom charge. After successful payment, the subscriber appears in the platform admin's queue with status pending. One click activates them: WordPress user is created, all vc_* meta keys are populated, an email is dispatched with temporary credentials. The client subdomain is then provisioned manually with the appropriate base + module plugin installation.

Smaller details that compound

Decisions almost no one would notice.

Account dropdowns never expose balances

When selecting a "from" account or "to" account anywhere on the platform, the dropdown shows only "BankName — AccountName." Balances are deliberately omitted. This is a security UX decision — preventing shoulder-surfing, accidental disclosure during screen-sharing, and reducing visual noise in the most-used UI surface on the entire dashboard.

Marketing copy lives in wp_options

Every word on the homepage — hero headline, testimonials, trust badges, social links, footer copy, custom CSS — is stored as rows in wp_options and editable from a single admin Settings page. The client can rewrite their public copy without touching a PHP file or risking a syntax error. This was a deliberate decision early in the build to avoid the most common ongoing-maintenance request.

Family financial health score

The Family module computes a household financial health score from savings rate, debt-to-income ratio, emergency-fund coverage, and goal progress. It's presented as a single number with a colored band — green, amber, red — alongside the next concrete action that would move it. Clarity over completeness.

No Composer, no npm, no build step

Vault Core has zero external dependencies beyond WordPress itself and Paystack's SDK (loaded as a single JS include). No Composer manifest, no package.json, no Webpack config, no Vite, no Tailwind compile step. Every change is a direct file upload. The codebase will run on any Hostinger plan at any time, today or three years from now, without dependency rot or build-pipeline maintenance. Boring, durable, deployable.

The outcome

Five plugins. Five weeks.

From first commit to all five plugins shipped: roughly five weeks of active development. Approximately 9,600 lines of pure PHP, JavaScript, and CSS — every line solo-authored. Public site live. Signup pipeline working. Build complete. Onboarding workflows for the first cohort of clients now in active preparation.

5
Custom plugins
shipped
~9.6k
Lines of code
solo-authored
8
African countries
tax-supported
5w
From commit
to build complete

What's next

Active workstreams: first client onboardings on dedicated subdomains, refining the platform admin reporting suite, expanding the tax engine to additional countries on request, and beginning a content engine that explains the modules' value to non-technical buyers — Nigerian SME owners, school finance officers, household financial decision-makers.

Read another case study · 01

A faith-based community platform with a three-stream referral system and currency-aware Paystack routing. 44 plugins, ~25 database tables, launched May 2026.