Skip to content
SaaS

Amica School Portal

amicaschoolportal.com

Amica replaces the spreadsheets, paper registers, and WhatsApp messages Nigerian private schools run on with one platform for fees, results, attendance, and parent communication, built to serve many schools on shared infrastructure without any one school's data ever being reachable from another's.

The Outcome

The Shift

Multi-tenant isolation is enforced at the database layer, independent of application code, and verified with real cross-tenant queries against a real Postgres test database rather than assumed to work. Separately, a dedicated billing-reliability pass closed a payment path that could let Paystack capture a real charge and never record it in Amica, caught with a test-mode payment during hardening, before any live school depended on it.

Key Outcomes
01

Cross-school data leakage is blocked at the database layer, tested by attempting the exact query that would cause it and asserting it fails.

02

A school in any billable state (pending, expired, or in its grace period) can always reach its own billing page to pay.

03

Every completed payment is guaranteed to be recorded, verified against a real Paystack test transaction before general use.

The Context

  • Manual processes don't scale, and they fail quietly. Fee tracking in spreadsheets, report cards typed and printed per term, payment confirmations sent by personal WhatsApp: the failure modes (lost records, double charges, missed notifications) are real, and nothing affordable was built for the actual operating context, Nigerian payment gateways, Naira as the unit, SMS delivery that has to work on feature phones.
  • A multi-school platform is a trust problem before it's a features problem. Once fee and result records for several schools live in the same system, a school considering the platform has to be convinced its students' data cannot leak to another school on the same infrastructure, before anything else about the product matters.
  • Billing has to work for a school that hasn't paid yet, not just one that has. A school in a pending, expired, or grace-period state still needs to reach its own billing page to fix that, or it has no way back to active use.

The Decision

The questions, trade-offs, and architectural reasoning behind how this product was built.

Decision Aug 2026

Closing the one path where a real payment could vanish

Subscription billing is gated by account status: schools in a pending, expired, or grace-period state are blocked from most of the app.

"That same gate was blocking the billing pages themselves, so a school that hadn't paid could never reach the one place that would let it. Fixing that surfaced a deeper issue: the payment callback route required a full session, but Paystack's redirect back to the app is a cross-site navigation that the app's strict cookie policy drops, so a real, successfully completed payment could be captured and never recorded, silently."
  • Loosen the cookie policy site-wide to keep the callback authenticated
  • Remove authentication from the callback route specifically and authorize it a different way

Loosening cookies site-wide would widen the blast radius of a fix meant for one route. Removing auth from just the callback was safer, provided the route's new authorization boundary (a Paystack-verified transaction plus server-set metadata Paystack only ever echoes back) was actually as strong as a login.

Chosen Approach

Removed authentication from the billing callback route, and treated everything that removal put at risk as its own problem to close in the same change: every failure branch was collapsed into one identical response so a caller can't fingerprint valid references by the error text, the incidental rate-limiting a login step provided was replaced with an explicit IP-based limit that fails open (not closed) so a Redis outage can't itself cause a real payment to be dropped, and every request now writes an audit log line before anything else runs.

Impact

The subscription now activates correctly regardless of session state, verified against a real Paystack test-mode payment, not a mocked test, before shipping.

Takeaway

Found and closed during a dedicated hardening pass, before a live school ever relied on it.

Decision Feb 2026

Choosing database-enforced isolation over trusting application code

Three approaches were viable for keeping one school's data invisible to another: separate databases per school, application-layer scoping only, or PostgreSQL Row-Level Security.

"Separate databases maximize isolation but make cross-school reporting hard and get expensive at scale. Application-layer scoping is simplest, but has zero defense if a developer forgets one where-clause."
  • Separate databases per school
  • Application-layer scoping only
  • PostgreSQL Row-Level Security with a single shared database

Defense-in-depth is the only acceptable strategy when handling another institution's financial and student records. Row-Level Security gives that without the operational cost of a database per school.

Chosen Approach

Application-layer scoping stays as the first line of defense, but Row-Level Security at the database layer is the one that actually has to fail for a leak to happen.

Impact

Testing this honestly required switching the test suite from SQLite to a real Postgres database, since SQLite doesn't support the session-parameter mechanism Row-Level Security relies on, closing a category of false-positive test passes in the process.

Takeaway

The database is the last line of defense, not the application code.

The Execution

  • PostgreSQL Row-Level Security as a second, independent isolation layer. Every tenant-scoped query is filtered by school at the application layer, but that alone only holds if every developer remembers the filter every time. A Postgres session parameter, set once per request before any query runs, means the database itself refuses to return another school's rows even if the application-layer filter is ever missed.
  • Kobo-only currency storage. Every monetary amount is stored as an integer in kobo, never a float or a decimal column, so rounding errors in fee calculations are eliminated by construction rather than caught in testing.
  • Horizon-managed background work from day one. PDF report card generation, SMS delivery, and bulk imports run as queued jobs behind Laravel Horizon, configured before a single job existed, so the pattern of "the queue can wait" never had a chance to take hold.
Amica School Portal screenshot 1 Amica School Portal screenshot 2 Amica School Portal screenshot 3 Amica School Portal screenshot 4 Amica School Portal screenshot 5

Client Feedback

What clients say after shipping.

“I’m proud of what ThinkDare has built. He listened closely to our needs and turned our vision into a portal that makes school administration smoother and keeps our community better connected. His skill, care, and commitment have made a real difference to Amica. We’re grateful for The work and excited about what we can accomplish together next.”
Niyi Adesanmi , V.P. Admin, Amica School Portal

Every project starts with a conversation about trade-offs, not technology.

Let's Talk