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