Thank you for your interest in contributing to StellarEduPay! This guide will help you get started with development, understand our conventions, and successfully submit your first pull request.
- Getting Started
- Development Setup
- Branch Naming Convention
- Commit Message Guidelines
- Coding Standards
- Testing Requirements
- Pull Request Process
- Architecture Guidelines
- Security Best Practices
Before you begin, ensure you have the following installed:
- Node.js (v18 or higher)
- npm (v9 or higher)
- MongoDB (v7 or higher) OR Docker (for containerized setup)
- Redis (v6 or higher) - required for BullMQ retry queue
- Git for version control
-
Fork and Clone the Repository
git clone https://github.com/YOUR_USERNAME/stellaredupay.git cd stellaredupay -
Install Dependencies
# Root dependencies (for testing) npm install # Backend dependencies cd backend npm install # Frontend dependencies cd ../frontend npm install cd ..
-
Configure Environment Variables
Copy the example environment files and configure them:
# Root environment cp .env.example .env # Backend environment cp backend/.env.example backend/.env # Frontend environment cp frontend/.env.example frontend/.env.local
-
Set Up Stellar Testnet Wallet
- Visit Stellar Laboratory
- Generate a new keypair for testing
- Fund your testnet wallet using Friendbot
- Add the public key to
SCHOOL_WALLET_ADDRESSin your.envfiles
-
Start Required Services
Option A: Using Docker (Recommended)
docker-compose up -d mongo redis
Option B: Local Installation
- Start MongoDB on port 27017
- Start Redis on port 6379
-
Run the Application
# Terminal 1: Start backend cd backend npm run dev # Terminal 2: Start frontend cd frontend npm run dev
-
Verify Setup
- Backend: http://localhost:5000/api/health
- Frontend: http://localhost:3000
For a complete containerized environment:
# Build and start all services
docker-compose up --build
# Run in detached mode
docker-compose up -d
# View logs
docker-compose logs -f backend
# Stop all services
docker-compose downIf you need to run database migrations:
cd backend
node migrations/001_backfill_remaining_balance.jsUse the following prefixes for your branches:
-
feature/- New features or enhancements- Example:
feature/add-payment-reminders
- Example:
-
fix/- Bug fixes- Example:
fix/transaction-polling-race-condition
- Example:
-
docs/- Documentation updates- Example:
docs/update-api-spec
- Example:
-
refactor/- Code refactoring without behavior changes- Example:
refactor/extract-stellar-client
- Example:
-
test/- Adding or updating tests- Example:
test/add-fee-adjustment-tests
- Example:
-
chore/- Maintenance tasks, dependency updates- Example:
chore/update-stellar-sdk
- Example:
Branch Naming Rules:
- Use lowercase with hyphens
- Be descriptive but concise
- Reference issue number when applicable:
fix/123-payment-validation
We follow Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
feat:- New featurefix:- Bug fixdocs:- Documentation changesstyle:- Code style changes (formatting, no logic change)refactor:- Code refactoringtest:- Adding or updating testschore:- Maintenance tasks
feat(payments): add payment reminder scheduler
Implements automated email reminders for overdue payments.
Includes configurable cooldown period and max reminder count.
Closes #45
---
fix(stellar): handle rate limit errors from Horizon API
Added exponential backoff retry logic when Horizon returns 429.
Fixes #67
---
docs(api): update payment endpoints documentation
Added examples for bulk payment verification endpoint.- Use present tense ("add feature" not "added feature")
- Use imperative mood ("move cursor to..." not "moves cursor to...")
- First line should be 50 characters or less
- Reference issues and PRs in the footer
- Use ES6+ syntax (async/await, arrow functions, destructuring)
- Use 2 spaces for indentation
- Use single quotes for strings
- Add semicolons at the end of statements
- Use camelCase for variables and functions
- Use PascalCase for classes and React components
- Use UPPER_SNAKE_CASE for constants
Backend Structure:
backend/src/
βββ routes/ # Express route definitions only
βββ controllers/ # Request/response handling, no business logic
βββ services/ # Business logic and external API calls
βββ models/ # Mongoose schemas and database models
βββ middleware/ # Express middleware (auth, validation, etc.)
βββ utils/ # Helper functions and utilities
βββ config/ # Configuration files
Frontend Structure:
frontend/src/
βββ pages/ # Next.js pages (routing)
βββ components/ # Reusable React components
βββ services/ # API client and external services
βββ styles/ # CSS and styling
- Keep functions small and focused - Each function should do one thing well
- Use meaningful variable names - Avoid abbreviations unless widely understood
- Add JSDoc comments for public functions and complex logic
- Handle errors gracefully - Always catch and log errors appropriately
- Avoid hardcoding values - Use environment variables or configuration files
- Validate inputs - Use Joi schemas for API validation
- Use async/await - Prefer async/await over raw promises
// Good
const calculateTotalFees = async (studentId, feeStructureId) => {
try {
const student = await Student.findById(studentId);
if (!student) {
throw new Error('Student not found');
}
const feeStructure = await FeeStructure.findById(feeStructureId);
return feeStructure.calculateTotal(student);
} catch (error) {
logger.error('Failed to calculate fees', { studentId, error });
throw error;
}
};
// Bad
const calc = (sid, fid) => {
return Student.findById(sid).then(s => {
return FeeStructure.findById(fid).then(f => f.calculateTotal(s));
});
};| Location | Purpose |
|---|---|
backend/tests/ |
Backend unit / service / controller tests |
tests/ |
Root cross-cutting suites only (e2e, integration, helpers) |
frontend/ |
Frontend tests (own package scripts) |
Rule: never put the same filename under both tests/ and backend/tests/.
# Root suite (tests/ only β not backend/tests)
npm testAll tests mock both the Stellar SDK and MongoDB β no real network connection or running database is required.
Expected output:
PASS tests/stellar.test.js
PASS tests/payment.test.js
PASS tests/payment-limits.test.js
...
Test Suites: X passed, X total
Tests: X passed, X total
Time: ~5s
npm test -- tests/stellar.test.js
npm test -- tests/payment.test.js
npm test -- tests/payment-limits.test.js# Backend (root Jest config β backend/src)
npm run coverage
# Frontend (frontend/jest.config.js β frontend/src)
npm run coverage:frontend
# Equivalent one-off form (works for either package):
npm test -- --coverageJest will print a per-file coverage table (lines, statements, functions and branches) and write a full report to:
coverage/backend/β backend source (backend/src)coverage/frontend/β frontend source (frontend/src)
Each directory contains lcov-report/index.html (browsable), lcov.info, coverage-summary.json and cobertura-coverage.xml. See Test Coverage for the measured baseline, thresholds, and the expectation for new code.
| File | Coverage |
|---|---|
tests/stellar.test.js |
Stellar service: asset detection, fee validation, amount normalisation, transaction verification, ledger sync |
tests/payment.test.js |
Payment API: full payment flow, all endpoints, edge cases, error handling |
tests/payment-limits.test.js |
Payment limits: validation, boundary cases, error codes |
Tests use mocks and do not require a real .env file. If you want to run the backend's own test suite separately:
cd backend
npm testThe backend tests also use mocks; no live MongoDB or Stellar Horizon connection is needed.
- New features must include unit tests
- Bug fixes should include regression tests
- API endpoints require integration tests
- Critical services (Stellar integration, payment processing) require comprehensive test coverage
npm run test:backend
- Be isolated and not depend on external services
- Mock Stellar SDK calls to avoid hitting the network
- Use descriptive test names that explain the scenario
- Follow the Arrange-Act-Assert pattern
Example Test:
describe('Payment Validation', () => {
it('should reject payments below minimum amount', async () => {
// Arrange
const payment = { amount: 0.001, studentId: 'STU001' };
// Act
const result = await validatePayment(payment);
// Assert
expect(result.valid).toBe(false);
expect(result.error).toContain('below minimum');
});
});StellarEduPay measures test coverage on every pull request (see issue #1288). Coverage is collected with Jest's instrumentation for backend/src (root Jest config) and frontend/src (frontend Jest config). The CI coverage job runs both and uploads the reports as downloadable artefacts on every PR, so reviewers can inspect exactly which code paths a change exercises.
Coverage is reported for four metrics β lines, statements, functions, and branches β and branch coverage is tracked and reported separately from line coverage. Every defect enumerated in #1288 is an unexecuted branch inside a function whose other branches are tested, so branch coverage is the signal that would have surfaced them; line coverage alone would have partially masked them.
| Command | Scope |
|---|---|
npm run coverage |
backend (backend/src) |
npm run coverage:frontend |
frontend (frontend/src) |
npm test -- --coverage |
whichever package you run it in |
Reports land in coverage/backend and coverage/frontend (lcov-report/index.html, lcov.info, coverage-summary.json, cobertura-coverage.xml).
The baseline below was captured from main and is recorded here and in issue #1288. It is preliminary: ~64 of 219 suites are currently failing (the blocked prerequisite in #1288), so the numbers understate real coverage. They will be re-measured and the floors raised once the suite is green.
| Area | Lines | Branches | Functions | Statements |
|---|---|---|---|---|
backend/src |
49.45% | 43.12% | 44.34% | 48.77% |
frontend/src |
39.37% | 47.27% | 23% | 38.2% |
The no-regression floors configured in the Jest configs (set at the measured baseline, with a small margin for run-to-run variance) are:
| Scope | Lines | Branches | Functions | Statements |
|---|---|---|---|---|
global β all of backend/src except services/, middleware/, controllers/ |
51% | 46% | 37% | 51% |
backend/src/services/ |
53% | 45% | 51% | 52% |
backend/src/middleware/ |
52% | 45% | 42% | 50% |
backend/src/controllers/ |
37% | 32% | 32% | 36% |
frontend/src (global) |
39% | 47% | 22% | 38% |
A coverageThreshold is set in each Jest config at (or slightly below) the measured baseline, so coverage cannot regress from the numbers above:
- Global floor β applies repo-wide. A PR that drops overall coverage below the baseline fails the coverage check.
- Per-directory floors for
backend/src/services/,backend/src/middleware/andbackend/src/controllers/β the money, auth and tenant-isolation code. These carry the same starting floor today, but are the areas intended to be held to a much higher bar (target β₯ 70%) once the suite is green, because error-handling and failover paths there are exactly what a payments platform depends on.
The CI
coveragejob is intentionally non-gating for now: with the suite red, a low baseline must not block merges. The thresholds still enforce "no further regression." When the suite is green, promote the job to a required check and raise the per-directory floors.
- New features and bug fixes must ship with tests (regression tests for fixes).
- Cover the error-handling and failover branches, not just the happy path β that is where the highest-risk, least-tested code lives.
- A PR should not reduce coverage below the global floor, and should aim to meet or exceed the threshold for the directory it touches.
- Coverage on changed lines is the primary review signal; treat a drop there as a request to add tests.
The ten lowest-covered modules under backend/src/services/ are tracked in follow-up issue #1303 β the concrete backlog this measurement produces.
-
Sync with main branch
git checkout main git pull origin main git checkout your-branch git rebase main
-
Run all checks locally
# Run tests (from project root) npm test # Check backend syntax node -c backend/src/app.js
-
Update documentation
- Update
docs/api-spec.mdif you changed API endpoints - Update
README.mdif you added new features or changed setup - Add inline code comments for complex logic
- Update
-
Push your branch
git push origin your-branch-name
-
Create Pull Request on GitHub
- Use a clear, descriptive title
- Reference the issue: "Closes #123" or "Fixes #456"
- Describe what changed and why
- Include screenshots for UI changes
- List any breaking changes
-
PR Template
## Description Brief description of changes ## Related Issue Closes #123 ## Changes Made - Added payment reminder scheduler - Updated email notification service - Added tests for reminder logic ## Testing - [ ] Unit tests added/updated - [ ] Manual testing completed - [ ] No console errors ## Screenshots (if applicable) [Add screenshots for UI changes]
Your PR must meet these criteria before merging:
- Tests passing - All existing and new tests must pass
- No merge conflicts - Rebase on latest main if needed
- Code review approved - At least one maintainer approval required
- Documentation updated - Relevant docs reflect your changes
- Follows conventions - Branch naming, commit messages, code style
- No sensitive data - No secrets, API keys, or PII in code
- Maintainers will review your PR within 2-3 business days
- Address any requested changes by pushing new commits
- Once approved, a maintainer will merge your PR
- Your branch will be automatically deleted after merge
We follow a strict Service-Oriented Architecture. To maintain the codebase:
Routes (backend/src/routes/)
- Define endpoints only
- Apply middleware (auth, validation, rate limiting)
- Delegate to controllers
Controllers (backend/src/controllers/)
- Extract request data
- Call service methods
- Format responses
- Do not put business logic here
Services (backend/src/services/)
- Contains all business logic
- Handles external API calls (Stellar Horizon, webhooks)
- Manages database transactions
- All Stellar-specific logic must go into
stellarService.js
Models (backend/src/models/)
- Mongoose schemas
- Database validation rules
- Instance methods for model-specific operations
Middleware (backend/src/middleware/)
- Authentication and authorization
- Request validation (Joi schemas)
- Error handling
- Rate limiting and idempotency
- Use Functional Components with React Hooks
- Style with Tailwind CSS (via
globals.css) - UI components in
frontend/src/components/ - Page-level logic in
frontend/src/pages/ - All backend communication through
frontend/src/services/api.js
When adding a new feature:
- Update API spec first - Document in
docs/api-spec.md - Create service layer - Add business logic to appropriate service
- Add controller - Create controller to handle HTTP requests
- Define routes - Add routes with proper middleware
- Add validation - Create Joi schema in
middleware/schemas/ - Write tests - Add unit and integration tests
- Update docs - Document in relevant markdown files
As a blockchain-based payment system, security is paramount:
- Never hardcode sensitive values (wallet addresses, secret keys, API keys)
- Refer to
.env.examplefiles for required configuration - Use
process.envto access environment variables - Never commit
.envfiles to version control
- Memo Validation: Always ensure
studentIdis passed correctly in the Stellar Memo field - Secret Keys: Never log or store secret keys. Only use public keys for ledger lookups
- Amount Validation: Use
amountNormalizer.jsfor safe decimal handling - Transaction Verification: Always verify transactions against the blockchain
- Use JWT authentication for protected endpoints
- Implement rate limiting on all public endpoints
- Validate all inputs with Joi schemas
- Use idempotency keys for payment operations
- Sanitize user inputs to prevent injection attacks
- Never log sensitive information (passwords, secret keys, PII)
- Follow GDPR principles for data handling
We maintain a high bar for reliability with comprehensive test coverage.
Unit Tests
- Test individual functions and utilities
- Mock external dependencies (Stellar SDK, database)
- Focus on edge cases and error handling
Integration Tests
- Test complete API flows
- Verify database operations
- Test middleware chains
Example Test Structure:
describe('Fee Adjustment Engine', () => {
beforeEach(() => {
// Setup test data
});
afterEach(() => {
// Cleanup
});
it('should apply early payment discount', async () => {
// Arrange
const payment = { amount: 100, dueDate: '2026-04-30' };
// Act
const adjusted = await feeAdjustmentEngine.apply(payment);
// Assert
expect(adjusted.finalAmount).toBe(95);
expect(adjusted.discountApplied).toBe(5);
});
});# Run all backend tests
cd backend
npm test
# Run specific test file
npm test -- tests/verify_fee_validation.js
# Run with coverage
npm test -- --coverage- All new features must include tests
- Bug fixes should include regression tests
- Tests must pass before PR can be merged
- Aim for meaningful test coverage, not just high percentages
General Rules:
- Use ES6+ syntax (async/await, arrow functions, destructuring)
- 2 spaces for indentation
- Single quotes for strings
- Semicolons at end of statements
- Max line length: 100 characters
Naming Conventions:
camelCasefor variables and functionsPascalCasefor classes and React componentsUPPER_SNAKE_CASEfor constants- Prefix private methods with underscore:
_privateMethod
File Naming:
camelCase.jsfor utilities and servicesPascalCase.jsxfor React componentskebab-case.jsfor routes and middleware
Functions:
- Keep functions small and focused (single responsibility)
- Use descriptive names that explain what the function does
- Limit parameters to 3-4; use options object for more
- Add JSDoc comments for public APIs
Error Handling:
- Always use try-catch for async operations
- Log errors with context using the logger utility
- Throw meaningful error messages
- Use custom error classes when appropriate
Comments:
- Write self-documenting code first
- Add comments for complex business logic
- Explain "why" not "what"
- Keep comments up-to-date with code changes
/**
* Processes a payment transaction and updates student balance
* @param {string} studentId - The student's unique identifier
* @param {Object} transaction - Stellar transaction object
* @returns {Promise<Object>} Updated payment record
*/
const processPayment = async (studentId, transaction) => {
try {
const student = await Student.findOne({ studentId });
if (!student) {
throw new Error(`Student not found: ${studentId}`);
}
const amount = amountNormalizer.fromStroops(transaction.amount);
const payment = await Payment.create({
studentId,
amount,
transactionHash: transaction.hash,
status: 'confirmed'
});
await student.updateBalance(amount);
logger.info('Payment processed', { studentId, amount, hash: transaction.hash });
return payment;
} catch (error) {
logger.error('Payment processing failed', { studentId, error });
throw error;
}
};- Sync with latest main branch
- All tests pass locally
- Code follows style guidelines
- Documentation is updated
- No console.log or debug code remains
- Environment variables are documented in
.env.example
Title Format:
<type>(<scope>): <description>
Examples:
feat(payments): add automated reminder system
fix(stellar): resolve transaction polling race condition
docs(api): update webhook integration guide
Description Template:
## Description
Clear description of what this PR does and why.
## Related Issue
Closes #123
## Changes Made
- Added payment reminder scheduler service
- Updated notification service with email templates
- Added reminder configuration to .env.example
## Testing Done
- [ ] Unit tests added for reminder logic
- [ ] Integration tests for email sending
- [ ] Manual testing with testnet wallet
- [ ] Verified no regression in existing features
## Screenshots
[If UI changes, add before/after screenshots]
## Breaking Changes
[List any breaking changes or migration steps needed]
## Checklist
- [ ] Tests passing
- [ ] Documentation updated
- [ ] No merge conflicts
- [ ] Follows coding standards
- [ ] Environment variables documentedYour PR will be reviewed for:
- Functionality - Does it solve the problem correctly?
- Code Quality - Is it readable, maintainable, and well-structured?
- Tests - Are there adequate tests with good coverage?
- Documentation - Are changes documented appropriately?
- Security - Are there any security concerns?
- Performance - Does it introduce performance issues?
- Breaking Changes - Are breaking changes clearly documented?
- Respond to all review comments
- Push additional commits to address feedback
- Mark conversations as resolved once addressed
- Request re-review when ready
Before your PR can be merged:
- At least one maintainer approval
- All CI checks passing (when CI is configured)
- All review comments addressed
- No merge conflicts with main
- Documentation complete
Stellar Service (stellarService.js)
- All Horizon API calls go here
- Transaction parsing and validation
- Memo extraction and decryption
- Use
stellarRateLimitedClient.jsfor rate-limited calls
Payment Service (paymentService.js)
- Payment creation and verification
- Student balance updates
- Payment status management
Transaction Service (transactionService.js)
- Transaction polling and syncing
- Retry logic for failed transactions
- Idempotency handling
- Use Mongoose models for all database operations
- Implement soft deletes (see
utils/softDelete.js) - Use transactions for multi-document updates
- Index frequently queried fields
- Validate data at schema level
- Follow RESTful conventions
- Use proper HTTP status codes
- Return consistent response format (see
utils/responseHelper.js) - Version APIs when making breaking changes
- Document all endpoints in
docs/api-spec.md
- Use centralized error handler middleware
- Return user-friendly error messages
- Log detailed error context for debugging
- Use appropriate HTTP status codes
When reviewing others' PRs:
- Be respectful and constructive
- Ask questions to understand the approach
- Suggest improvements, don't demand them
- Approve when code meets standards, even if not perfect
- Focus on correctness, security, and maintainability
- Stellar Documentation
- Stellar SDK for JavaScript
- MongoDB Best Practices
- Express.js Guide
- Next.js Documentation
- Questions? Open a GitHub Discussion
- Bug Reports? Open an Issue
- Security Concerns? Email security@yourproject.com (do not open public issues)
By contributing to StellarEduPay, you agree that your contributions will be licensed under the MIT License.
Thank you for helping us eliminate manual reconciliation and fraud in school payments!