A closed-loop digital payment platform powering real-time, ledger-driven transactions within controlled ecosystems.
EasyPay replaces slow, cash-heavy and manual mobile money workflows with a two-layer financial architecture: an internal ledger for sub-second day-to-day transactions, and M-Pesa (Daraja API) as the external on/off-ramp for deposits and withdrawals.
The platform connects four user roles — Students, Guardians, Merchants, and Administrators — through a unified API, supporting QR-based point-of-sale payments, programmable wallet controls, and a complete financial audit trail.
In institutional environments (schools, campuses, controlled marketplaces), traditional mobile money creates friction: every transaction requires manual STK push flows, there's no spending visibility, and merchants have no reliable settlement mechanism. EasyPay collapses that into a scan-and-go experience backed by a real-time internal ledger.
┌─────────────────────────────────────────────────────────┐
│ EasyPay Platform │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────┐ │
│ │ Students │ │Guardians │ │Merchants │ │Admin │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──┬───┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴────────────┘ │
│ │ │
│ ┌───────────▼────────────┐ │
│ │ Django REST API │ │
│ │ (JWT Auth + RBAC) │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌───────────────┼────────────────┐ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐ │
│ │ Internal │ │ QR Token │ │ Wallet & │ │
│ │ Ledger │ │ Engine │ │ Balances │ │
│ └─────────────┘ └────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
│
┌───────────▼────────────┐
│ M-Pesa Daraja API │
│ STK Push │ B2C │
└────────────────────────┘
Internal layer handles all day-to-day transactions atomically (sub-second, no external API calls).
External layer (M-Pesa) is only invoked on deposit (STK Push) and withdrawal (B2C). This keeps costs low and speeds high.
- Closed-Loop Payments — funds enter via M-Pesa and circulate internally through the ledger; no external API call per transaction
- QR Token Payments — time-sensitive QR codes enable scan-and-go merchant payments (intent expires in 60 seconds)
- Smart Wallets — multiple wallet types per user (
MEAL,POCKET,PERSONAL,SETTLEMENT) with programmable controls - Multi-Role RBAC — four distinct roles (Student, Guardian, Merchant, Admin) with scoped permissions and tailored endpoints
- Guardian Controls — parents can link to student accounts, top up wallets, and monitor transaction history
- POS Terminal Management — merchants register and manage physical terminals; payments are initiated per-device
- Real-Time Ledger — all transactions are processed atomically with full audit trails and account statements
- JWT Authentication — short-lived access tokens + long-lived refresh tokens with token blacklisting on logout
| Layer | Technology |
|---|---|
| Framework | Django + Django REST Framework |
| Authentication | JWT (SimpleJWT) with token blacklisting |
| Database | PostgreSQL (UUID primary keys throughout) |
| Payment Gateway | M-Pesa Daraja API (STK Push + B2C) |
| API Style | RESTful with role-based access control |
- Python 3.11+
- PostgreSQL
- An active Safaricom Daraja API account (for M-Pesa features)
# Clone the repository
git clone https://github.com/Reagan-dev/EasyPay.git
cd EasyPay
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txtCreate a .env file in the project root. See .env.example for all required variables:
# Django
SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
# Database
DB_NAME=easypay_db
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_HOST=localhost
DB_PORT=5432
# M-Pesa Daraja API
MPESA_CONSUMER_KEY=your-consumer-key
MPESA_CONSUMER_SECRET=your-consumer-secret
MPESA_SHORTCODE=your-shortcode
MPESA_PASSKEY=your-passkey
MPESA_CALLBACK_URL=https://yourdomain.com/api/payments/mpesa/callback/# Apply migrations
python manage.py migrate
# Create a superuser (for admin access)
python manage.py createsuperuser
# Start the development server
python manage.py runserverThe API will be available at http://localhost:8000.
All endpoints are prefixed with /api/. Authentication uses Bearer tokens in the Authorization header.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/accounts/register/ |
Register a new user with role | No |
POST |
/api/accounts/login/ |
Login, returns JWT tokens | No |
POST |
/api/accounts/logout/ |
Blacklist refresh token | Required |
POST |
/api/accounts/token/refresh/ |
Refresh access token | No |
GET |
/api/accounts/me/ |
Get authenticated user profile | Required |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/wallets/ |
List all wallets with balances | Required |
GET |
/api/wallets/{wallet_type}/ |
Get wallet by type (MEAL, POCKET, etc.) | Required |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/payments/intent/create/ |
Create a QR payment intent (expires 60s) | Required |
GET |
/api/payments/intent/{uuid}/ |
Poll payment intent status | Required |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/merchants/profile/ |
Get business profile + terminals | Required |
PATCH |
/api/merchants/profile/ |
Update business profile | Required |
GET |
/api/merchants/terminals/ |
List registered POS terminals | Required |
POST |
/api/merchants/terminals/ |
Register a new POS terminal | Required |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/guardians/profile/ |
Get guardian profile | Required |
GET |
/api/guardians/students/ |
List linked students | Required |
POST |
/api/guardians/students/ |
Link a student by reg number | Required |
curl -X POST http://localhost:8000/api/accounts/login/ \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123"
}'{
"user": {
"id": "uuid-string",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"roles": [{ "role": "STUDENT" }]
},
"tokens": {
"access": "eyJ0eXAiOiJKV1Qi...",
"refresh": "eyJ0eXAiOiJKV1Qi..."
}
}curl -X POST http://localhost:8000/api/payments/intent/create/ \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"terminal": "uuid-of-registered-terminal",
"amount": 150.00
}'| Role | Description |
|---|---|
STUDENT |
End user — holds wallets, makes payments via QR |
GUARDIAN |
Links to students, tops up wallets, monitors transactions |
MERCHANT |
Operates POS terminals, receives payments, manages settlement |
ADMIN |
Full system access — manages users, disputes, and platform settings |
| Wallet | Purpose |
|---|---|
MEAL |
Restricted to food/canteen merchants |
POCKET |
General-purpose spending wallet |
PERSONAL |
Personal savings or secondary wallet |
SETTLEMENT |
Merchant settlement wallet |
EasyPay/
├── accounts/ # User registration, login, JWT auth
├── students/ # Student profiles and management
├── guardians/ # Guardian profiles and student linking
├── merchants/ # Business profiles and POS terminals
├── wallets/ # Wallet models, balances, and ledger
├── payments/ # Payment intents, M-Pesa callbacks, QR logic
├── core/ # Shared utilities, base models, permissions
├── config/ # Django settings and URL routing
├── .env.example # Environment variable template
├── requirements.txt # Python dependencies
└── manage.py
Why closed-loop? Every external payment API call (M-Pesa STK Push) adds 3-10 seconds of latency and incurs a transaction cost. By maintaining an internal ledger, EasyPay reduces day-to-day transaction time to sub-second while cutting operational costs dramatically.
Why UUID primary keys? UUIDs prevent enumeration attacks on financial records — a sequential integer ID leaks how many transactions exist in the system.
Why QR intents expire in 60 seconds? Time-limited payment tokens prevent replay attacks at POS terminals. A merchant generates the intent; the student's QR scan must happen within the window.
This project is licensed under the MIT License. See LICENSE for details.
Built with Django REST Framework · PostgreSQL · M-Pesa Daraja API