Skip to main content

(01)Case Study

A banking-system simulation where ownership and uncertainty stay explicit

A backend banking-system simulation built for a training assignment, connected to the simulated NibssByPhoenix NIBSS API. It is not production banking infrastructure and uses synthetic test identities only.

// overview

EgoPay exposes a REST API for registration, BVN/NIN KYC verification, account creation, balance checks, name enquiry, intra-bank and inter-bank transfers, transaction status, and transaction history. All banking operations are delegated to NibssByPhoenix, the simulated NIBSS sandbox API.

The training assignment required a complete customer journey while keeping customer data isolated and avoiding guesses about external transaction outcomes. The README explicitly frames the result as a simulation exercise, not production banking infrastructure.

// at a glance

Project
EgoPay backend simulation
Context
Training assignment
External API
Simulated NibssByPhoenix NIBSS API
Persistence
PostgreSQL via Prisma

// measured outcomes

Sender ownership
Server-derived
The authenticated session determines the debited account; client-supplied from/account identifiers are not trusted.
Transfer state
PENDING first
Timeouts remain pending until the upstream TSQ status can establish the outcome.
Incoming payments
Ledger reconciliation
Polling compares the live ledger with the local cache to detect credits the app did not initiate.
Account cardinality
One per customer
A database unique constraint backs the service-level check.

Research

The project is structured around the sandbox contract: customer registration and KYC precede account creation, name enquiry precedes transfers, and the upstream TSQ endpoint supplies later transaction status. BVN and NIN flows use the sandbox identity store and synthetic test identities.

Planning

The backend uses a modular-monolith shape: Express routers forward to controllers, services hold business rules, the NibssByPhoenix client is the only module that calls the external API, and Prisma with PostgreSQL handles persistence.

Design

The companion frontend is a React and Vite web app with an OPay-inspired mobile interface. This case study focuses on the backend contract and the rules that keep the customer journey scoped to the authenticated customer.

Architecture

Node.js and strict TypeScript power an Express 5 API. Zod validates input, Prisma persists records in PostgreSQL, and a dedicated NibssByPhoenix client normalizes upstream responses into typed contracts. JWT authenticates customers while a separate fintech JWT authenticates EgoPay to the sandbox.

Development

The implementation separates transport from business logic and persistence. The sender for every transfer comes from the authenticated session; the request body accepts the recipient, amount, and narration rather than a client-selected source account.

Security

Customer identity comes exclusively from the verified JWT, and ownership-scoped queries prevent cross-customer access. One account per customer is enforced by a database unique constraint. Passwords use bcrypt, inputs use Zod, and Helmet, a CORS allowlist, rate limiting, request limits, and Pino sensitive-field redaction are part of the security boundary. KYC numbers are masked in responses, and only synthetic test identities are used.

// how it's built

A full-stack system, end to end

Requests move from Express routers to controllers and services. Services apply ownership and transaction rules, then use the NibssByPhoenix client for KYC, account, balance, name-enquiry, transfer, and TSQ calls. Prisma records the local customer, account, and transaction state.

HTTP

01

Express 5 API

Routes, controllers, Zod validation

The API boundary handles request validation, authentication middleware, rate limiting, error handling, and customer-scoped endpoints.

Rules

02

Service layer

Ownership, transfers, reconciliation

Business rules derive identity from the verified JWT, record transfer state before external calls, and reconcile incoming ledger changes.

Integration

03

NibssByPhoenix client

Typed sandbox API contracts

One client owns external HTTP calls, fintech-token refresh, timeout handling, and normalization of inconsistent upstream response shapes.

Performance

External calls use timeouts and normalized error handling. A transfer is recorded locally as PENDING before the upstream call and changes to SUCCESS or FAILED only from the upstream response; a timeout remains PENDING because its outcome is unknown. Incoming payments are found by polling and reconciling the live ledger against the local balance cache.

Lessons learned

The project demonstrates why ownership should be derived server-side, why uncertain external outcomes should remain explicit, and why balance reconciliation is needed when an upstream API provides no incoming-transactions webhook or listing.

// outcome

What shipped

A documented training exercise that connects a customer-facing banking journey to a simulated NIBSS sandbox while keeping account ownership, transaction state, and synthetic-identity boundaries explicit.

The README lists known sandbox limitations, including transient upstream failures, in-memory rate limits, and the lack of webhooks for incoming payments. These constraints are part of the simulation framing, not claims about a production banking deployment.

Node.jsTypeScriptExpress 5PostgreSQLPrismaZodJWTbcryptHelmetPino

// available for projects and roles

Need a platform with institutional standards?

Tell me what you're building and I'll reply with clear next steps. Usually within 24 hours.