Mira is a college payments and partner payout platform built for hackathon submission. It lets student-facing payment forms initiate Nomba checkout links, routes payments through configured partner sub-accounts, records payment history in PostgreSQL, and exposes an admin portal for partner onboarding, balances, settlement bank setup, withdrawals, and reconciliation.
.
|-- Backend/ Express API, payment orchestration, PostgreSQL schema, Redis queues
|-- Mira/ React + Vite frontend for student payments and admin dashboard
|-- docker-compose.yml
`-- README.md
- Student payment initiation with Nomba sandbox checkout links.
- Partner account model for colleges, departments, and associations.
- PostgreSQL-backed payments, partner accounts, split rules, withdrawals, and admin users.
- Redis/BullMQ-backed withdrawal queue.
- Admin JWT authentication.
- Partner dashboard endpoints for balances, payment history, withdrawals, and reconciliation.
- Webhook endpoints for payment confirmation and disbursement updates.
- Local Docker Compose setup for PostgreSQL and Redis.
Frontend:
- React 18
- Vite
- TypeScript
- Tailwind CSS
- shadcn/Radix UI components
Backend:
- Node.js
- Express
- PostgreSQL
- Redis
- BullMQ
- Nomba sandbox API
- JWT authentication
- Pino logging
- Node.js 20 or newer
- npm
- Docker Desktop with WSL integration enabled, if running on Windows/WSL
- Nomba sandbox credentials
Install dependencies in both projects. There is no root-level package.json.
cd Backend
npm cicd ../Mira
npm ciStart PostgreSQL and Redis:
cd <repository-root>
docker compose up -dStart the backend:
cd Backend
npm run devStart the frontend:
cd Mira
npm run devDefault local URLs:
- Frontend:
http://localhost:8080 - Backend:
http://localhost:3000 - PostgreSQL:
localhost:5432 - Redis:
localhost:6379
Create Backend/.env:
NODE_ENV=development
PORT=3000
PGHOST=localhost
PGPORT=5432
PGUSER=mira
PGPASSWORD=mira_dev_password
PGDATABASE=mira_dev
REDIS_URL=redis://127.0.0.1:6379
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
JWT_SECRET=
JWT_EXPIRES_IN=1h
BANK_ENCRYPTION_KEY=
NOMBA_CLIENT_ID=
NOMBA_CLIENT_SECRET=
NOMBA_PARENT_ACCOUNT_ID=
NOMBA_SUB_ACCOUNT_ID=
NOMBA_SIGNATURE_KEY=
PAYMENT_CALLBACK_URL=http://localhost:8080/receipts
EMAIL_HOST=
EMAIL_PORT=
EMAIL_USER=
EMAIL_PASS=
SYSTEM_EMAIL=
DEVELOPER_EMAIL=
SLACK_WEBHOOK_URL=Create Mira/.env.local:
VITE_API_URL=http://localhost:3000
VITE_BACK_URL=http://localhost:3000
VITE_Nomba_PUBLIC_KEY=Generate a local JWT secret:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"The backend initializes the PostgreSQL schema on startup from Backend/config/initDb.js.
On a fresh database it creates and seeds:
- Partner accounts
- Partner sub-accounts
- Split rules
- Admin users
- Payment and withdrawal tables
- Ledger and reconciliation tables
Seeded local admin users:
admin@Mira.com / password123
colerm_admin@Mira.com / password123
These are development credentials only. Replace them before any real deployment.
Payment initiation:
POST /api/payments/initiateExample request:
{
"email": "student@example.com",
"payerName": "Test Student",
"amount": 3000,
"partnerIdentifier": "COLERM",
"businessVertical": "education",
"metadata": {
"matricNumber": "20201234",
"level": "200",
"mainLevel": "Staylite"
},
"callbackUrl": "http://localhost:8080/receipts"
}Other useful endpoints:
GET /
GET /api/payments/confirm/:reference
GET /api/payments/receipts/:receiptNumber
POST /api/payments/webhook/:gateway
POST /api/admin/auth/register
POST /api/admin/auth/login
GET /api/admin/partner/balance
GET /api/admin/partner/payments
POST /api/admin/partner/bank-lookup
POST /api/admin/partner/bank-account
POST /api/admin/partner/withdraw
GET /api/admin/partner/withdrawals
POST /api/admin/reconcile
The OpenAPI spec is available at:
Backend/docs/openapi.yaml
Health check:
curl -i http://localhost:3000/Initiate a payment:
curl -i -X POST http://localhost:3000/api/payments/initiate \
-H "Content-Type: application/json" \
--data '{
"email": "test@example.com",
"payerName": "Test User",
"amount": 3000,
"partnerIdentifier": "COLERM",
"businessVertical": "education",
"metadata": {
"matricNumber": "20201234",
"level": "200",
"mainLevel": "Staylite"
},
"callbackUrl": "http://localhost:8080/receipts"
}'Admin login:
curl -i -X POST http://localhost:3000/api/admin/auth/login \
-H "Content-Type: application/json" \
--data '{
"email": "colerm_admin@Mira.com",
"password": "password123"
}'400 from /api/payments/initiate can mean:
- The request failed Joi validation, usually invalid email, missing payer name, or
amount <= 0. - The partner has no active Nomba sub-account configured.
- Nomba returned an error and the controller wrapped it as a
400. - Live environment variables do not match the local setup.
404 from /api/payments/initiate usually means:
partnerIdentifierdoes not exist or is inactive in the database.
Browser CORS failures usually mean:
- The frontend origin is not listed in
Backend/server.jsCORS options.
For production or hosted demo environments, configure these values on the hosting provider:
- PostgreSQL connection variables
- Redis connection variables
- JWT secret
- Nomba credentials
- Payment callback URL
- Frontend
VITE_API_URL
The frontend API client reads VITE_API_URL and falls back to the Railway API URL currently in Mira/src/lib/api.ts.
The fastest way to validate the core submission path is:
- Start Docker services.
- Start the backend.
- Start the frontend.
- Submit a COLERM or COLPHYS payment from the frontend.
- Confirm the backend returns a Nomba sandbox checkout URL.
- Log in as
colerm_admin@Mira.comand verify the payment appears in partner payment history.
The project favors an end-to-end working hackathon demo: local service orchestration, payment initiation, partner records, admin authentication, and dashboard reads are the primary evaluation paths.