Wallet is a RESTful transaction API designed to handle real financial operations with the seriousness they deserve. It implements two-phase debit authorization, HMAC-SHA256 request signing, multi-currency support, batch processing, and full admin controls - all built with Node.js, Express, Prisma, and MySQL.
This is not a toy CRUD app with a balance field. It is a system where every transaction has a state machine, every request can be cryptographically verified, and every mutation leaves an audit trail.
Why I Built Wallet
Financial APIs are one of the hardest things to get right in software. The domain demands precision, security, and reliability at every layer. A single rounding error, a missed race condition, or a weak auth flow can have real consequences.
I wanted to build something that demonstrates genuine understanding of financial software patterns - not just the happy path, but the edge cases:
- Two-phase authorization with authorize → debit/reverse patterns for safe fund holds.
- HMAC-SHA256 request signing with replay protection for cryptographic integrity.
- Precision arithmetic using
monetrav2.3.0 to eliminate floating-point errors. - Idempotent operations with referenceId tracking for safe retries.
System Architecture
Wallet follows a layered architecture with clear separation of concerns. Each layer has a single responsibility and dependencies flow in one direction.
Domain Layer
The domain layer contains the core business logic, completely independent of HTTP or database concerns:
wallet.mjshandles wallet state, balance calculations, and freeze/unfreeze logic.transactions.mjsmanages the transaction state machine - authorize, debit, credit, and reverse operations with enforced state transitions.
Handler Layer
Request handlers map HTTP requests to domain operations:
- Auth handlers - registration, login, and current user retrieval with JWT tokens.
- Wallet handlers - deposit, withdraw, balance checks, and transaction history.
- Transaction handlers - authorize, debit, credit, and reverse endpoints.
- Admin handlers - user listing, wallet oversight, freeze/unfreeze, and admin reversals.
Service Layer
Services coordinate between handlers and the database:
auth.service.mjsmanages user registration, login validation, and JWT issuance.wallet.service.mjshandles wallet creation, balance queries, and state management.transaction.service.mjsorchestrates the full transaction lifecycle.
Middleware Layer
Express middleware handles cross-cutting concerns:
auth.mjs- JWT verification and user extraction.rbac.mjs- role-based access control for customer and admin routes.idempotency.mjs- duplicate request detection via referenceId.signature.mjs- HMAC-SHA256 request signing verification.requestLogger.mjs- structured request/response logging.
Infrastructure Layer
- Prisma ORM with MySQL for type-safe database access.
- Redis for rate limiting, caching, and idempotency key storage.
- Structured logging, metrics collection, and alerting infrastructure.
Key Features
Two-Phase Debit Authorization
The core transaction pattern separates fund reservation from fund transfer:
- Authorize - places a hold on funds. The amount is reserved but still belongs to the wallet.
- Debit - completes the authorization, actually transferring the reserved funds.
- Reverse - cancels the authorization, releasing the hold back to available balance.
This is the same pattern used by payment processors like Stripe. It prevents double-spending and gives merchants a window to cancel before funds are captured.
HMAC-SHA256 Request Signing
For high-security environments, every request can be cryptographically signed. The server verifies the signature using a shared secret, ensuring requests have not been tampered with in transit. Combined with nonce-based replay protection, this guards against both modification and duplicate submission attacks.
Multi-Currency Support
The API supports 10 major currencies with automatic conversion. All arithmetic uses monetra for precise decimal operations - no floating-point rounding errors, no silent precision loss.
Batch Transactions
Multiple transactions can be processed atomically in a single request. Either all operations succeed or none do - there is no partial state.
Webhook Notifications
Real-time event notifications for transaction state changes. External systems can subscribe to wallet events and react immediately.
Rate Limiting
Comprehensive rate limiting protects against abuse. Limits are applied per-user and per-endpoint, with Redis-backed sliding window counters.
Transaction States
Every transaction follows a controlled state machine:
| State | Meaning |
|---|---|
| pending | Authorization created, funds reserved but not captured |
| completed | Transaction finalized, funds transferred |
| reversed | Authorization cancelled, funds released |
Direct operations (deposits and withdrawals) complete immediately. Two-phase operations transition through pending before reaching a terminal state.
API Design
The API follows RESTful conventions with versioned endpoints under /api/v1:
| Category | Endpoints |
|---|---|
| Auth | Register, login, current user |
| Wallets | Balance, details, transactions, deposit, withdraw |
| Transactions | Authorize, debit, credit, reverse |
| Admin | User management, wallet oversight, freeze/unfreeze, admin reversals |
Interactive Swagger documentation is available at /api-docs when the server is running.
Security
Security is foundational, not bolted on:
| Measure | Implementation |
|---|---|
| Authentication | JWT tokens with configurable expiry |
| Authorization | Role-based access control (Customer / Admin) |
| Request Integrity | HMAC-SHA256 signing with nonce-based replay protection |
| SQL Injection | Prisma ORM with parameterized queries |
| Idempotency | referenceId tracking prevents duplicate processing |
| Rate Limiting | Redis-backed per-user, per-endpoint limits |
| Precision Math | monetra library eliminates floating-point errors |
| Audit Trail | Full ledger with balance snapshots for every mutation |
Technical Stack
- Runtime: Node.js 20.x
- Framework: Express
- ORM: Prisma
- Database: MySQL 8.x
- Cache: Redis 7.x
- Auth: JWT with bcrypt password hashing
- Math: monetra v2.3.0 for financial precision
- Testing: 201 tests (unit + E2E)
- Docs: Swagger UI at
/api-docs
Testing
The test suite covers 201 tests across unit and end-to-end layers:
- Unit tests verify domain logic, middleware behaviour, services, and repositories in isolation.
- E2E tests run full API flows against a live server - registration, deposits, withdrawals, two-phase transactions, and admin operations.
What I Learned
Building Wallet reinforced critical lessons about financial software:
- Precision is non-negotiable. Floating-point arithmetic fails silently in financial contexts. Using a dedicated decimal library from day one prevented an entire class of bugs.
- State machines prevent impossible states. Modelling transactions as explicit state transitions (pending → completed/reversed) eliminates ambiguity and makes the system auditable.
- Idempotency is a feature, not an optimization. Network retries happen. Without idempotency keys, a retried deposit becomes a double deposit. The referenceId pattern makes every operation safely repeatable.
- Security layers compound. JWT alone is not enough. Adding HMAC signing, replay protection, rate limiting, and RBAC creates defence in depth that no single layer could provide.
Where Can I Learn More?
- Repository: GitHub Repo
- Documentation: Comprehensive docs in the docs/ folder covering architecture, API reference, authentication, database schema, testing, deployment, monitoring, and scaling.