(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
01Express 5 API
Routes, controllers, Zod validation
The API boundary handles request validation, authentication middleware, rate limiting, error handling, and customer-scoped endpoints.
Rules
02Service layer
Ownership, transfers, reconciliation
Business rules derive identity from the verified JWT, record transfer state before external calls, and reconcile incoming ledger changes.
Integration
03NibssByPhoenix 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.
// 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.