Kijumbe is a mobile application built with Flutter to help users manage group savings systems known as "Mchezo" in Tanzania. This guide is intended for backend developers who will be implementing the Node.js API to support this application. This document outlines the key features, data models, API requirements, and integration points for the application.
The Kijumbe app follows a feature-based architecture on the frontend with a clear separation of concerns:
lib/
├── core/ # Core utilities, constants, and widgets
├── features/ # Main features of the app
│ ├── auth/ # Authentication feature
│ ├── groups/ # Group management feature
│ ├── home/ # Home screen feature
│ ├── navigation/ # Navigation components
│ ├── payments/ # Payment processing feature
│ └── profile/ # User profile feature
└── main.dart # App entry point
The backend should be structured to support this architecture with RESTful endpoints organized by feature.
The app uses phone-based authentication with OTP verification. The backend should implement:
// User Model example
const userSchema = new mongoose.Schema({
userId: String,
phoneNumber: String,
firstName: String,
lastName: String,
email: String,
photoUrl: String,
createdAt: Date,
updatedAt: Date
});Required endpoints:
POST /api/auth/request-otp: Send OTP to a phone numberPOST /api/auth/verify-otp: Verify OTP and authenticate userPOST /api/auth/profile: Create or update user profileGET /api/auth/profile: Get user profilePOST /api/auth/logout: End user session
Groups are central to the application. Each group has members, contributions, payouts, and rules.
// Group Model example
const groupSchema = new mongoose.Schema({
id: String,
name: String,
purpose: String,
description: String,
contributionAmount: Number,
frequency: {
type: String,
enum: ['daily', 'weekly', 'biweekly', 'monthly']
},
startDate: Date,
endDate: Date,
memberLimit: Number,
payoutMethod: {
type: String,
enum: ['sequential', 'random', 'byNeed', 'custom']
},
status: {
type: String,
enum: ['active', 'completed', 'cancelled']
},
members: [memberSchema],
contributions: [contributionSchema],
payouts: [payoutSchema],
rules: [ruleSchema],
createdBy: String,
createdAt: Date,
updatedAt: Date
});
// Group Member Model
const memberSchema = new mongoose.Schema({
id: String,
userId: String,
name: String,
photoUrl: String,
phoneNumber: String,
role: {
type: String,
enum: ['admin', 'treasurer', 'member']
},
joinedDate: Date,
hasReceivedPayout: Boolean,
isRemoved: Boolean,
removedDate: Date,
joinStatus: {
type: String,
enum: ['original', 'lateJoiner', 'replacement']
},
catchUpContributionIds: [String],
hasPaidCatchUp: Boolean
});
// Contribution Model
const contributionSchema = new mongoose.Schema({
id: String,
groupId: String,
memberId: String,
amount: Number,
dueDate: Date,
paidDate: Date,
status: {
type: String,
enum: ['pending', 'completed', 'late', 'missed']
},
receiptId: String,
paymentMethod: String,
penaltyAmount: Number
});
// Payout Model
const payoutSchema = new mongoose.Schema({
id: String,
groupId: String,
memberId: String,
amount: Number,
scheduledDate: Date,
processedDate: Date,
status: {
type: String,
enum: ['scheduled', 'processing', 'completed', 'cancelled']
},
transactionId: String,
paymentMethod: String
});
// Group Rule Model
const ruleSchema = new mongoose.Schema({
id: String,
groupId: String,
title: String,
description: String,
type: {
type: String,
enum: ['latePenalty', 'missedPaymentPenalty', 'attendance', 'other']
},
penaltyAmount: Number,
isActive: Boolean
});Required endpoints:
GET /api/groups: Get all available groupsGET /api/groups/user/:userId: Get groups for a specific userGET /api/groups/:id: Get a specific group by IDPOST /api/groups: Create a new groupPUT /api/groups/:id: Update a groupDELETE /api/groups/:id: Delete a groupPOST /api/groups/:id/join: Join an existing groupPOST /api/groups/:id/members: Add a new member to a groupPOST /api/groups/:id/members/late: Add a late-joining member with catch-up calculations
The app handles payments for contributions with a focus on mobile money in Tanzania:
// Payment Model
const paymentSchema = new mongoose.Schema({
id: String,
userId: String,
groupId: String,
contributionId: String,
amount: Number,
feeAmount: Number,
dateInitiated: Date,
dateCompleted: Date,
status: {
type: String,
enum: ['pending', 'processing', 'completed', 'failed', 'cancelled']
},
method: {
type: String,
enum: ['mobileMoney', 'bankTransfer', 'cash', 'other']
},
transactionId: String,
receiptUrl: String,
notes: String,
metadata: Object
});
// Receipt Model
const receiptSchema = new mongoose.Schema({
id: String,
paymentId: String,
groupId: String,
userId: String,
userName: String,
groupName: String,
amount: Number,
feeAmount: Number,
date: Date,
transactionId: String,
receiptNumber: String,
paymentMethod: String,
additionalInfo: Object
});
// Upcoming Payment Model
const upcomingPaymentSchema = new mongoose.Schema({
id: String,
groupId: String,
groupName: String,
amount: Number,
dueDate: Date,
isPaid: Boolean
});Required endpoints:
GET /api/payments/user/:userId: Get a user's payment historyGET /api/payments/group/:groupId: Get a group's payment historyGET /api/payments/:id: Get a specific paymentPOST /api/payments: Make a paymentGET /api/payments/upcoming/:userId: Get upcoming payments for a userGET /api/payments/:id/receipt: Get a receipt for a paymentPOST /api/payments/mobile-money/init: Initialize a mobile money paymentPOST /api/payments/mobile-money/verify: Verify a mobile money payment
The app supports multiple mobile money providers in Tanzania. The backend should implement:
- Integration with M-Pesa API
- Integration with Tigo Pesa API
- Integration with Airtel Money API
- Webhook endpoints for payment notifications
Example mobile money initialization endpoint:
/**
* Initialize a mobile money payment
* @param {string} provider - The mobile money provider (e.g., 'mpesa', 'tigopesa', 'airtelmoney')
* @param {string} phoneNumber - The phone number for the transaction
* @param {number} amount - The amount to pay
* @param {string} description - Payment description
* @returns {object} Payment initialization details with a reference number
*/
app.post('/api/payments/mobile-money/init', async (req, res) => {
try {
const { provider, phoneNumber, amount, description } = req.body;
// Call the appropriate provider API
const result = await mobileMoneyService.initiatePayment(provider, phoneNumber, amount, description);
res.status(200).json({
success: true,
referenceNumber: result.referenceNumber,
message: `Payment request sent to ${phoneNumber}. Please check your phone to confirm.`
});
} catch (error) {
res.status(500).json({
success: false,
message: `Payment initialization failed: ${error.message}`
});
}
});The app includes a feature for adding members who join late to a group. The backend needs to implement the logic for calculating catch-up contributions:
/**
* Calculate missed contributions for a late-joining member
* @param {Object} group - The group object
* @param {Date} joinDate - The date the member is joining
* @returns {number} Number of missed contributions
*/
function calculateMissedContributions(group, joinDate) {
// If member joined before the group started, they haven't missed any
if (joinDate <= group.startDate) {
return 0;
}
// Calculate time since group started
const duration = joinDate - group.startDate;
const durationDays = Math.floor(duration / (1000 * 60 * 60 * 24));
// Calculate based on contribution frequency
switch (group.frequency) {
case 'daily':
return durationDays;
case 'weekly':
return Math.ceil(durationDays / 7);
case 'biweekly':
return Math.ceil(durationDays / 14);
case 'monthly':
// Calculate approximate months
return Math.floor((durationDays + 15) / 30);
default:
return 0;
}
}The app applies transaction fees to payments. The backend should implement this logic:
/**
* Calculate transaction fee
* @param {number} amount - The transaction amount
* @returns {number} The fee amount
*/
function calculateTransactionFee(amount) {
const feePercentage = 2.0; // 2% fee
const minimumFee = 500.0; // 500 TZS minimum
const maximumFee = 10000.0; // 10,000 TZS maximum
// Calculate fee as percentage of transaction amount
let fee = (amount * feePercentage) / 100.0;
// Apply minimum fee
if (fee < minimumFee && amount > minimumFee) {
fee = minimumFee;
}
// Apply maximum fee
if (fee > maximumFee) {
fee = maximumFee;
}
// Round to nearest whole number
return Math.round(fee);
}The backend should implement:
- JWT-based authentication
- Phone verification using SMS services (e.g., Twilio, Africa's Talking)
- Rate limiting for OTP requests
- Secure storage of user data
- Input validation and sanitization
Example JWT implementation:
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;
// Generate JWT token
function generateToken(userId) {
return jwt.sign({ userId }, JWT_SECRET, { expiresIn: '7d' });
}
// Verify JWT token middleware
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.status(401).json({ message: 'Authentication required' });
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Invalid or expired token' });
req.user = user;
next();
});
}For the Node.js backend, we recommend this structure:
backend/
├── config/ # Configuration files
├── controllers/ # Route handlers
│ ├── authController.js
│ ├── groupController.js
│ ├── paymentController.js
│ └── ...
├── middleware/ # Custom middleware
│ ├── auth.js # Authentication middleware
│ └── ...
├── models/ # Database models
│ ├── User.js
│ ├── Group.js
│ ├── Payment.js
│ └── ...
├── routes/ # Route definitions
│ ├── auth.js
│ ├── groups.js
│ ├── payments.js
│ └── ...
├── services/ # Business logic
│ ├── authService.js
│ ├── groupService.js
│ ├── paymentService.js
│ ├── mobileMoneyService.js
│ └── ...
├── utils/ # Utility functions
│ ├── dateUtils.js
│ ├── currencyUtils.js
│ └── ...
├── app.js # Express application setup
└── server.js # Server entry point
We recommend a consistent API response format:
// Success response
{
"success": true,
"data": {
// Response data here
},
"message": "Operation successful"
}
// Error response
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Error message"
}
}Required environment variables:
# Server configuration
PORT=3000
NODE_ENV=development
API_URL=http://localhost:3000
# Database
MONGO_URI=mongodb://localhost:27017/kijumbe
# Authentication
JWT_SECRET=your_jwt_secret
OTP_EXPIRY_TIME=300 # seconds
# SMS Service (for OTP)
SMS_API_KEY=your_sms_api_key
SMS_SENDER_ID=KIJUMBE
# Mobile Money APIs
MPESA_API_KEY=your_mpesa_api_key
MPESA_API_SECRET=your_mpesa_api_secret
TIGO_API_KEY=your_tigo_api_key
AIRTEL_API_KEY=your_airtel_api_key
We recommend implementing comprehensive tests for the API:
- Unit tests for utility functions and services
- Integration tests for API endpoints
- Load testing for payment processing
- Use environment variables for configuration
- Implement proper error logging
- Set up monitoring for the API
- Configure CORS for the Flutter app
- Implement rate limiting
- Set up database backups
The Flutter app expects:
- RESTful endpoints with JSON responses
- JWT authentication via Authorization header
- Proper HTTP status codes (200, 201, 400, 401, 403, 404, 500)
- Consistent error messages
This guide should provide a solid foundation for implementing the backend services required by the Kijumbe app. The key focus areas are authentication, group management, and payment processing with special attention to mobile money integration and late joiner calculations.
For any questions or clarifications, please refer to the frontend code repository or contact the development team.