Fortify Bank

Case study · Personal Project · Aug 2026

Fortify Bank

A cash flow app that actually moves money, not just tracks it.

Built with Next.js, Plaid, and Dwolla. Still in progress — updated as it ships.

Next.jsAuthFintechSecurity

The problem

I've used a budgeting spreadsheet for over a year. It's a template from Etsy, and it's genuinely good: I can see every account in one place, and project my balance out a full year, paycheck by paycheck. Every app I tried before it got one thing wrong: they'd roll money into the next pay period that wasn't actually going to be there, because they couldn't handle a bill landing on a slightly different date each month. They also struggled with semi-monthly paychecks.

The spreadsheet solved that. What it can't do is move money. I still open my bank app separately to act on anything it tells me.

Fortify is what happens if you don't accept that gap. The moment you learn something about your money and the moment you can act on it should be the same tap, not two separate apps.

Fortify Bank landing screen
The landing screen. The projected balance — not the current one — is the number the product is organised around.

My role

Solo project: product decisions, frontend, data modelling, and the integration layer are all mine, which means nobody else said no for me. Where that changed the work, I've said so; the persona gate below exists because there was no PM to hold scope.

What I did not do: this is not production software with users. There is no traffic, no uptime record, and no impact metric I could honestly report. The claims here are about process and architecture, because that's what actually exists.

Frontend decisions

One persona, not a segmented set

Every feature got checked against a single question: does this reduce Maya's mental math? If not, it's not v1. This kept scope honest in a project with no PM to say no for me.

A recurring-payment model borrowed from something that already worked

The forecasting feature is the hardest part of this project, and I didn't start from a blank page. My own spreadsheet already solved it: a recurring rule (name, frequency, amount, start date, end date) plus per-occurrence exceptions for the one month a bill lands on a weird date. I am designing Fortify's data model around that pattern instead of trying to auto-detect recurring transactions from raw history, a harder and less reliable problem than it looks.

One server action owns money movement

After the audit, the entire transfer flow sits behind a single server action. The client never touches a raw bank or user record; it sends an intent and receives a result. That boundary is enforced in one place instead of trusted in many.

Two data paths, one component API

Guest mode renders the same dashboard from hardcoded constants instead of live Plaid data. The branch lives at the data-fetching layer: a component rendering a transaction list shouldn't know whether the data is real or mocked, it should just render what it receives. That boundary gets harder to hold as more components touch account data, so it's something I stay deliberate about.

Fortify Bank security settings
Security settings after the pass. The shareable account ID is now AES-256-GCM, not base64.

Tradeoffs

Named the missing providers instead of faking the features

Dwolla can't do currency exchange, and card-based top-ups need a second provider. Rather than quietly building a worse version of those, I documented which real provider each one would require in production and drew a clear line around what the current stack genuinely supports.

Hardcoded guest data over generated or mock-DB data

In a demo context, predictable beats dynamic. Hardcoded constants tell a controlled story (a consistent first impression every time) where random dummy data introduces inconsistency exactly where you want clarity.

A KYC wall I couldn't design away

Plaid and Dwolla require identity data even in sandbox, so signup would have had to ask for name, address, DOB, and the last four of an SSN. I built for technical compliance first and under-served the person who just wanted to see the dashboard. Guest mode is the correction, not a feature I planned.

Splitting the God component back up

One AuthForm handled signup, signin, forgot-password, and reset-password through conditional rendering. Shared styles turned into tangled validation and submission logic — resetting a password has no business sharing logic with signup collection. DRY became write-everything-twice, and I broke it apart.

Quality

Zod validates every money-moving surface at the edge. Sentry watches production. Decisions land in version-controlled ADRs so a reviewer can read why before reading how. The security audit itself was a scheduled pass, not a lucky catch — that's the part I'd repeat on a team.

What's missing: there's no automated test suite on Fortify yet. The transfer server action and the recurring-rule expansion are the two places that deserve tests first, because both are pure logic with money-shaped consequences.

Result & what's next

Registration, login, bank linking, and money movement all actually happen — the Plaid and Dwolla handshakes are solid, and ACH transfers, and a real balance work end to end in sandbox. The forecast, the reason the product exists, is still in progress.

  • Plaid OAuth and two-factor auth, now that the data boundary is fixed.
  • Scope guest-mode route access — transfers and settings need to degrade cleanly or be blocked outright.
  • Decouple the bank link from the auth session so an expired session doesn't read as a vanished account.