Skip to content

Reagan-dev/EasyPay

Repository files navigation

EasyPay

A closed-loop digital payment platform powering real-time, ledger-driven transactions within controlled ecosystems.

Python Django DRF PostgreSQL JWT M-Pesa


Overview

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.

The Problem It Solves

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.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    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.


Key Features

  • 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

Tech Stack

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

Getting Started

Prerequisites

Installation

# 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.txt

Environment Variables

Create 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/

Run the Application

# Apply migrations
python manage.py migrate

# Create a superuser (for admin access)
python manage.py createsuperuser

# Start the development server
python manage.py runserver

The API will be available at http://localhost:8000.


API Overview

All endpoints are prefixed with /api/. Authentication uses Bearer tokens in the Authorization header.

Authentication

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

Wallets

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

Payments

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

Merchants

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

Guardians

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

Example: Login Request

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..."
  }
}

Example: Create Payment Intent (Merchant POS)

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
  }'

User Roles

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 Types

Wallet Purpose
MEAL Restricted to food/canteen merchants
POCKET General-purpose spending wallet
PERSONAL Personal savings or secondary wallet
SETTLEMENT Merchant settlement wallet

Project Structure

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

Design Decisions

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.


License

This project is licensed under the MIT License. See LICENSE for details.


Built with Django REST Framework Β· PostgreSQL Β· M-Pesa Daraja API

About

Closed-loop digital payment platform with M-Pesa integration and QR-based POS payments

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages