A production-grade multi-currency payment platform supporting fiat (NGN) and cryptocurrency (BTC, ETH, USDT, BNB, TRX) transactions with a ledger-first architecture.
Note: This is a portfolio project demonstrating payment system architecture. It does not process real funds.
- Multi-Currency Wallets - NGN, USDT, BTC, ETH, BNB, TRX
- Ledger-First Design - Balances derived from immutable ledger entries
- Double-Entry Bookkeeping - Every transaction is balanced and auditable
- Fiat Integration - NGN deposits via Paystack
- Crypto Integration - Deposits/withdrawals via NOWPayments
- Internal Transfers - Instant wallet-to-wallet transfers
- JWT Authentication - Secure token rotation with refresh tokens
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │────▶│ Backend API │────▶│ PostgreSQL │
│ (Next.js) │ │ (Express) │ │ (Ledger DB) │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Paystack │ │ NOWPayments │
│ (NGN Fiat) │ │ (Crypto) │
└─────────────────┘ └─────────────────┘
Core Principles:
- Balances are NEVER stored directly - always calculated from ledger entries
- All financial operations are atomic (all-or-nothing)
- Idempotency keys prevent duplicate transactions
- Webhooks are signature-verified before processing
| Layer | Technology |
|---|---|
| Backend | Node.js, Express 5, TypeScript |
| Database | PostgreSQL, Prisma 7 |
| Auth | JWT, bcrypt |
| Validation | Zod |
| Fiat Payments | Paystack |
| Crypto Payments | NOWPayments |
| Frontend | Next.js 16, React 19, Tailwind |
| Document | Description |
|---|---|
| Architecture | System design, components, principles |
| Database | Schema, tables, relationships, queries |
| API Specification | Complete REST API reference |
| Business Flows | User journeys, transaction flows |
| Security Model | Auth, threats, mitigations |
| Implementation Plan | Status, phases, code examples |
- Node.js 20+
- PostgreSQL 15+
- pnpm
cd backend
# Install dependencies
pnpm install
# Configure environment
cp .env.example .env
# Edit .env with your database URL and API keys
# Setup database
pnpm prisma generate
pnpm prisma migrate dev
# Run development server
pnpm devcd frontend
# Install dependencies
pnpm install
# Run development server
pnpm dev# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/zorex_pay
# JWT Secrets
JWT_ACCESS_SECRET=your-32-char-min-secret
JWT_REFRESH_SECRET=your-32-char-min-secret
# Paystack (NGN)
PAYSTACK_SECRET_KEY=sk_test_xxx
PAYSTACK_PUBLIC_KEY=pk_test_xxx
# NOWPayments (Crypto)
NOWPAYMENTS_API_KEY=your-api-key
NOWPAYMENTS_IPN_SECRET=your-ipn-secret
# System
SYSTEM_SETTLEMENT_LEDGER_ID=uuid-of-settlement-account
API_URL=http://localhost:5500
FRONTEND_URL=http://localhost:3000
PORT=5500POST /api/register- Create accountPOST /api/login- AuthenticatePOST /api/refresh- Refresh tokensPOST /api/logout- End session
GET /api/wallets- List user walletsPOST /api/wallets- Create new walletGET /api/wallets/:id- Wallet detailsGET /api/wallets/:id/transactions- Transaction history
POST /api/funding/bank- NGN via PaystackGET /api/funding/crypto/address- Get deposit address
POST /api/transfers- Wallet-to-wallet transferPOST /api/transfers/username- Transfer by username
POST /api/payouts/bank- NGN withdrawalPOST /api/payouts/crypto- Crypto withdrawal
POST /api/webhooks/paystack- Paystack notificationsPOST /api/webhooks/nowpayments- NOWPayments notifications
zorex-pay/
├── backend/
│ ├── controllers/ # HTTP request handlers
│ ├── services/ # Business logic
│ ├── repository/ # Data access layer
│ ├── middleware/ # Auth, validation
│ ├── routes/ # Express routes
│ ├── prisma/ # Database schema
│ ├── errors/ # Domain errors
│ ├── validators/ # Zod schemas
│ └── models/ # TypeScript types
├── frontend/
│ ├── app/ # Next.js app router
│ └── components/ # React components
├── docs/ # Documentation
│ ├── ARCHITECTURE.md
│ ├── DATABASE.md
│ ├── API.md
│ ├── FLOWS.md
│ ├── SECURITY.md
│ └── IMPLEMENTATION.md
└── README.md
| Feature | Status |
|---|---|
| Database Schema | Complete |
| Authentication | Complete |
| NGN Funding (Paystack) | Complete |
| Crypto Funding | In Progress |
| Internal Transfers | In Progress |
| Payouts | In Progress |
| Admin Dashboard | Planned |
See Implementation Plan for detailed status.
Traditional approach stores balance as a field:
wallet.balance = 10000 // Vulnerable to drift, no audit trail
Ledger-first approach derives balance:
SELECT SUM(CASE WHEN direction='CREDIT' THEN amount ELSE -amount END)
FROM ledger_entries WHERE wallet_id = ?
-- Always accurate, fully auditableEvery transaction affects two accounts:
User deposits 10,000 NGN:
├── CREDIT: User wallet +10,000
└── DEBIT: Settlement -10,000
──────────────────────
NET: 0 (always balanced)
This ensures:
- No money created from nothing
- Complete audit trail
- Easy reconciliation
Each refresh token is single-use:
- User refreshes → new tokens issued
- Old refresh token marked as revoked
- Prevents token reuse attacks
- Enables session tracking
This is a portfolio project. Feel free to explore and learn from the code.
MIT