A microservice for evaluating fees and commissions on financial transactions. Built with Clean Architecture principles using .NET 9, supporting multiple transaction types with flexible rule-based calculation engine.
- Flexible Rule Engine: Define flat fees, percentage-based fees, or combinations with min/max constraints
- Multi-Transaction Support: PAYMENT, TRANSFER, WITHDRAWAL, DEPOSIT, FX, LOAN
- Conditional Logic: Rules based on amount ranges, customer tiers, regions, currencies, and custom JSON conditions
- Time-Based Activation: Set effective date ranges for rules with automatic activation/deactivation
- Priority System: Control rule application order with configurable priority levels
- Commission Management: Separate agent/partner commission calculation with multi-tier support
- Clean Architecture: Domain-driven design with clear separation of concerns
- PostgreSQL 17: JSONB support for flexible rule conditions with optimized indexing
- Docker Ready: Complete containerization with docker-compose orchestration
- Health Checks: Built-in health endpoints for monitoring and orchestration
- API Documentation: Interactive Swagger/OpenAPI documentation
- Comprehensive Testing: Unit and integration tests with high coverage
- .NET 9 SDK (9.0 or later)
- Docker Desktop (for containerized setup)
- PostgreSQL 17 (or use Docker Compose)
- Entity Framework Core Tools (for migrations)
# Install EF Core tools globally
dotnet tool install --global dotnet-ef --version 9.0.0FeeEngine/
βββ src/
β βββ FeeEngine.Api/ # Web API layer (Controllers, DTOs, Middleware)
β βββ FeeEngine.Application/ # Business logic, services, interfaces
β βββ FeeEngine.Domain/ # Domain entities, value objects, enums
β βββ FeeEngine.Infrastructure/ # Data access, repositories, EF Core, migrations
βββ tests/
β βββ FeeEngine.UnitTests/ # Unit tests for services and logic
β βββ FeeEngine.IntegrationTests/ # API integration tests
βββ .github/
β βββ workflows/
β βββ ci.yml # GitHub Actions CI/CD pipeline
βββ docker-compose.yml # Docker orchestration
βββ .gitignore
βββ FeeEngine.sln
βββ README.md
# Clone the repository
git clone https://github.com/your-org/FeeEngine.git
cd FeeEngine
# Start all services (API + PostgreSQL + PgAdmin)
docker-compose up -d
# Check logs
docker-compose logs -f feeengine-api
# Stop services
docker-compose down
# Stop and remove volumes (fresh start)
docker-compose down -vService URLs:
- API: http://localhost:5001
- Swagger UI: http://localhost:5001
- Health Check: http://localhost:5001/health
- PgAdmin: http://localhost:5050 (admin@feeengine.com / admin)
- PostgreSQL: localhost:5433 (feeuser / P@ssword123)
# 1. Restore dependencies
dotnet restore
# 2. Update connection string in appsettings.Development.json
# Example: "Host=localhost;Port=5433;Database=feeengine;Username=feeuser;Password=P@ssword123"
# 3. Run database migrations
cd src/FeeEngine.Infrastructure
dotnet ef database update --startup-project ../FeeEngine.Api
# 4. Run the API
cd ../FeeEngine.Api
dotnet run
# API will be available at: https://localhost:7000 or http://localhost:5000cd src/FeeEngine.Infrastructure
# Create migration
dotnet ef migrations add <MigrationName> --startup-project ../FeeEngine.Api
# Example
dotnet ef migrations add AddFeeRuleIndexes --startup-project ../FeeEngine.Api# Apply to database
dotnet ef database update --startup-project ../FeeEngine.Api
# Apply specific migration
dotnet ef database update <MigrationName> --startup-project ../FeeEngine.Api
# Rollback to previous migration
dotnet ef database update <PreviousMigrationName> --startup-project ../FeeEngine.Api# Remove last migration (if not applied to database)
dotnet ef migrations remove --startup-project ../FeeEngine.Api# See all migrations
dotnet ef migrations list --startup-project ../FeeEngine.Api# Run all tests
dotnet test
# Run with detailed output
dotnet test --verbosity detailed
# Run with coverage
dotnet test /p:CollectCoverage=true /p:CoverageReporter=html /p:CoverageReporterDirectory=coverage
# Run specific test project
dotnet test tests/FeeEngine.UnitTests
dotnet test tests/FeeEngine.IntegrationTests
# Run specific test
dotnet test --filter "FullyQualifiedName~FeeEvaluatorTests.Evaluate_WithFlatFee_ReturnsCorrectAmount"curl -X POST http://localhost:5001/api/fees/evaluate \
-H "Content-Type: application/json" \
-d '{
"transactionType": "PAYMENT",
"amount": 100.00,
"currency": "USD",
"metadata": {
"customerTier": "premium",
"region": "domestic"
}
}'Response:
{
"totalFees": 3.50,
"currency": "USD",
"breakdown": [
{
"ruleId": "uuid-here",
"ruleName": "Standard Payment Fee",
"amount": 2.50,
"type": "PAYMENT",
"description": "Flat: 2.50 Rate: 1.5%"
}
]
}curl -X POST http://localhost:5001/api/commissions/evaluate \
-H "Content-Type: application/json" \
-d '{
"transactionType": "PAYMENT",
"amount": 100.00,
"currency": "USD",
"agentType": "AGENT",
"metadata": {}
}'curl -X POST http://localhost:5001/api/feerules \
-H "Content-Type: application/json" \
-d '{
"ruleName": "International Transfer Fee",
"transactionType": "TRANSFER",
"flatAmount": 10.00,
"percentage": 0.02,
"minAmount": 0,
"maxAmount": 999999,
"currency": "USD",
"conditionsJson": "{\"region\": \"international\"}",
"priority": 100,
"isActive": true,
"effectiveFrom": "2024-01-01T00:00:00Z"
}'# Get all fee rules
curl http://localhost:5001/api/feerules
# Get specific rule by ID
curl http://localhost:5001/api/feerules/{rule-id}curl -X PUT http://localhost:5001/api/feerules/{rule-id} \
-H "Content-Type: application/json" \
-d '{
"flatAmount": 15.00,
"percentage": 0.025,
"isActive": true
}'curl -X DELETE http://localhost:5001/api/feerules/{rule-id}curl http://localhost:5001/health
# Response: Healthy| Type | Description | Use Case |
|---|---|---|
| PAYMENT | Standard payment processing | Card payments, bill payments |
| TRANSFER | Domestic and international transfers | Wire transfers, P2P transfers |
| WITHDRAWAL | ATM and counter withdrawals | Cash withdrawals |
| DEPOSIT | Account deposits | Cash deposits, check deposits |
| FX | Foreign exchange transactions | Currency conversion fees |
| LOAN | Loan disbursement and processing | Loan origination fees |
{
"ruleName": "ATM Withdrawal Fee",
"transactionType": "WITHDRAWAL",
"flatAmount": 2.00,
"percentage": 0,
"minAmount": 0,
"maxAmount": 999999,
"currency": "USD",
"priority": 100
}{
"ruleName": "FX Commission",
"transactionType": "FX",
"flatAmount": 0,
"percentage": 0.025,
"minAmount": 0,
"maxAmount": 999999,
"currency": "USD",
"priority": 100
}{
"ruleName": "Premium International Transfer",
"transactionType": "TRANSFER",
"flatAmount": 5.00,
"percentage": 0.01,
"conditionsJson": "{\"customerTier\": \"premium\", \"region\": \"international\"}",
"minAmount": 100,
"maxAmount": 10000,
"currency": "USD",
"priority": 50
}Create multiple rules with different amount ranges and priorities:
[
{
"ruleName": "Small Payment Fee",
"transactionType": "PAYMENT",
"flatAmount": 1.00,
"minAmount": 0,
"maxAmount": 50,
"priority": 100
},
{
"ruleName": "Medium Payment Fee",
"transactionType": "PAYMENT",
"flatAmount": 2.00,
"minAmount": 50.01,
"maxAmount": 500,
"priority": 100
},
{
"ruleName": "Large Payment Fee",
"transactionType": "PAYMENT",
"flatAmount": 5.00,
"percentage": 0.005,
"minAmount": 500.01,
"maxAmount": 999999,
"priority": 100
}
]# Database Connection
ConnectionStrings__Default=Host=localhost;Port=5433;Database=feeengine;Username=feeuser;Password=P@ssword123
# ASP.NET Core
ASPNETCORE_ENVIRONMENT=Development
ASPNETCORE_URLS=http://+:8080
# Logging
Logging__LogLevel__Default=Information
Logging__LogLevel__Microsoft.AspNetCore=Warning
Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Information{
"ConnectionStrings": {
"Default": "Host=localhost;Port=5433;Database=feeengine;Username=feeuser;Password=YourPassword"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"AllowedHosts": "*"
}βββββββββββββββββββββββββββββββββββββββββββ
β API Layer (Controllers) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Application Layer (Services, DTOs) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Domain Layer (Entities, Rules) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Infrastructure (EF Core, Repositories) β
βββββββββββββββββββββββββββββββββββββββββββ
Layer Responsibilities:
-
Domain (FeeEngine.Domain)
- Core business entities:
FeeRule,CommissionRule,FeeTransaction - Value objects and enums
- Business rule validation
- No dependencies on other layers
- Core business entities:
-
Application (FeeEngine.Application)
- Use cases and business logic
- Interfaces for repositories
- Service implementations:
FeeEvaluator,CommissionEvaluator - DTOs for data transfer
- Depends only on Domain
-
Infrastructure (FeeEngine.Infrastructure)
- Database context and migrations
- Repository implementations
- External service integrations
- Depends on Application and Domain
-
API (FeeEngine.Api)
- REST controllers
- Request/Response DTOs
- Middleware for error handling
- Swagger configuration
- Depends on all layers
- Repository Pattern: Abstracts data access logic
- Dependency Injection: Loose coupling and testability
- Strategy Pattern: Rule evaluation algorithms
- CQRS Principles: Separation of read/write operations
- Factory Pattern: Entity creation
- Specification Pattern: Filtering and querying logic
FeeRules Table:
Id(UUID, PK)RuleName(string, indexed)TransactionType(string, indexed)FlatAmount(decimal)Percentage(decimal)MinAmount,MaxAmount(decimal)Currency(string)ConditionsJson(JSONB)Priority(int, indexed)IsActive(bool, indexed)EffectiveFrom,EffectiveTo(timestamp, indexed)CreatedAt,UpdatedAt(timestamp)
CommissionRules Table:
- Similar structure with additional
AgentTypefield
Indexes:
- Composite index on
(TransactionType, IsActive, EffectiveFrom) - Index on
Priorityfor sorting - JSONB indexes on
ConditionsJsonfor queries
# Build image
docker build -t feeengine:latest -f src/FeeEngine.Api/Dockerfile .
# Run container
docker run -d -p 5001:8080 \
-e ConnectionStrings__Default="Host=db;Database=feeengine;Username=feeuser;Password=yourpass" \
-e ASPNETCORE_ENVIRONMENT=Production \
--name feeengine-api \
feeengine:latest
# View logs
docker logs -f feeengine-apiCreate docker-compose.prod.yml:
version: "3.9"
services:
feeengine-db:
image: postgres:17
environment:
POSTGRES_DB: feeengine
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
restart: always
feeengine-api:
image: feeengine:latest
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__Default=Host=feeengine-db;Database=feeengine;Username=${DB_USER};Password=${DB_PASSWORD}
ports:
- "80:8080"
depends_on:
- feeengine-db
restart: always
volumes:
postgres-data:apiVersion: apps/v1
kind: Deployment
metadata:
name: feeengine
spec:
replicas: 3
selector:
matchLabels:
app: feeengine
template:
metadata:
labels:
app: feeengine
spec:
containers:
- name: feeengine
image: feeengine:latest
ports:
- containerPort: 8080
env:
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: feeengine-secrets
key: db-connection-string
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5- Secrets Management: Use environment variables or secret managers (Azure Key Vault, AWS Secrets Manager)
- Connection Strings: Never commit credentials to source control
- API Authentication: Add JWT/OAuth2 authentication for production
- Input Validation: Request validation at API layer
- SQL Injection: Protected by EF Core parameterization
- Rate Limiting: Implement API rate limiting for production
- HTTPS: Always use HTTPS in production
- Database Indexing: Strategic indexes on frequently queried columns
- Async/Await: Throughout the stack for I/O operations
- Connection Pooling: Built into Npgsql
- Query Optimization: EF Core with
AsNoTracking()for read-only queries - JSONB Indexes: PostgreSQL GIN indexes on JSONB columns
- Caching: Consider adding Redis for frequently accessed rules
- Follow C# Coding Conventions
- Write unit tests for new features
- Update documentation as needed
- Keep commits atomic and well-described
# Clean build artifacts
dotnet clean
# Remove all bin/obj folders
find . -type d -name "bin" -o -name "obj" | xargs rm -rf
# Update NuGet packages
dotnet list package --outdated
dotnet add package <PackageName> --version <Version>
# Database cleanup
dotnet ef database drop --startup-project src/FeeEngine.Api- .NET 9 Documentation
- Entity Framework Core 9
- PostgreSQL JSONB
- Clean Architecture
- API Design Best Practices
Issue: Connection refused to database
# Check if PostgreSQL is running
docker ps | grep postgres
# Check connection string in appsettings
# Verify port (5433 for Docker, 5432 for local PostgreSQL)Issue: Migration not found
# Rebuild and reapply migrations
dotnet clean
dotnet build
dotnet ef database update --startup-project src/FeeEngine.ApiIssue: Port already in use
# Check what's using the port
lsof -i :5001 # macOS/Linux
netstat -ano | findstr :5001 # Windows
# Kill the process or use a different port in docker-compose.ymlThis project is licensed under the MIT License - see the LICENSE file for details.
For issues and questions:
- GitHub Issues: Create an issue
- Email: support@feeengine.com
- Documentation: Wiki
- Built with .NET 9
- Database: PostgreSQL 17
- ORM: Entity Framework Core 9
- API Documentation: Swashbuckle
Built with β€οΈ using .NET 9 and Clean Architecture
Version 1.0.0 | Last Updated: December 2024
- Flexible Rule Engine: Define flat fees, percentage-based fees, or combinations
- Multi-Transaction Support: PAYMENT, TRANSFER, WITHDRAWAL, DEPOSIT, FX, LOAN
- Conditional Logic: Rules based on amount ranges, customer tiers, regions, currencies
- Time-Based Activation: Set effective dates for rules
- Priority System: Control rule application order
- Commission Management: Separate agent/partner commission calculation
- Clean Architecture: Domain-driven design with clear separation of concerns
- PostgreSQL: JSONB support for flexible rule conditions
- Docker Ready: Complete containerization setup
- Comprehensive Testing: Unit and integration tests included
- .NET 9 SDK
- Docker Desktop
- PostgreSQL 17 (or use Docker Compose)
FeeEngine/
βββ src/
β βββ FeeEngine.Api/ # Web API layer
β βββ FeeEngine.Application/ # Business logic & services
β βββ FeeEngine.Domain/ # Domain entities & value objects
β βββ FeeEngine.Infrastructure/ # Data access & external services
βββ tests/
β βββ FeeEngine.UnitTests/ # Unit tests
β βββ FeeEngine.IntegrationTests/ # Integration tests
βββ docker-compose.yml
βββ FeeEngine.sln
# Clone the repository
git clone <repository-url>
cd FeeEngine
# Start all services
docker-compose up -d
# Check logs
docker-compose logs -f feeengine
# API will be available at: http://localhost:5001
# Swagger UI: http://localhost:5001
# PgAdmin: http://localhost:5050# Restore dependencies
dotnet restore
# Update connection string in appsettings.Development.json
# Run migrations
dotnet ef database update --project src/FeeEngine.Infrastructure --startup-project src/FeeEngine.Api
# Run the API
cd src/FeeEngine.Api
dotnet run
# API will be available at: https://localhost:7000 or http://localhost:5001# Create a new migration
dotnet ef migrations add MigrationName --project src/FeeEngine.Infrastructure --startup-project src/FeeEngine.Api
# Apply migrations
dotnet ef database update --project src/FeeEngine.Infrastructure --startup-project src/FeeEngine.Api
# Remove last migration (if not applied)
dotnet ef migrations remove --project src/FeeEngine.Infrastructure --startup-project src/FeeEngine.Api# Run all tests
dotnet test
# Run with coverage
dotnet test /p:CollectCoverage=true /p:CoverageReporter=html
# Run specific test project
dotnet test tests/FeeEngine.UnitTests
dotnet test tests/FeeEngine.IntegrationTestscurl -X POST http://localhost:5001/api/fees/evaluate \
-H "Content-Type: application/json" \
-d '{
"transactionType": "PAYMENT",
"amount": 100.00,
"currency": "USD",
"customerTier": "premium",
"region": "domestic"
}'Response:
{
"totalFees": 3.50,
"currency": "USD",
"breakdown": [
{
"ruleId": "...",
"ruleName": "Standard Payment Fee",
"amount": 2.50,
"type": "PAYMENT",
"description": "Flat: 2.50 Rate: 1.5%"
}
]
}curl -X POST http://localhost:5001/api/commissions/evaluate \
-H "Content-Type: application/json" \
-d '{
"transactionType": "PAYMENT",
"amount": 100.00,
"currency": "USD",
"agentType": "AGENT"
}'curl -X POST http://localhost:5001/api/feerules \
-H "Content-Type: application/json" \
-d '{
"ruleName": "International Transfer Fee",
"transactionType": "TRANSFER",
"flatAmount": 10.00,
"percentage": 0.02,
"minAmount": 0,
"maxAmount": 999999,
"currency": "USD",
"conditionsJson": "{\"region\": \"international\"}",
"priority": 100
}'curl http://localhost:5001/api/feerulescurl -X PUT http://localhost:5001/api/feerules/{rule-id} \
-H "Content-Type: application/json" \
-d '{
"flatAmount": 15.00,
"isActive": true
}'curl -X DELETE http://localhost:5001/api/feerules/{rule-id}- PAYMENT: Standard payment processing
- TRANSFER: Domestic and international transfers
- WITHDRAWAL: ATM and counter withdrawals
- DEPOSIT: Account deposits
- FX: Foreign exchange transactions
- LOAN: Loan disbursement and processing
{
"ruleName": "ATM Withdrawal Fee",
"transactionType": "WITHDRAWAL",
"flatAmount": 2.00,
"minAmount": 0,
"maxAmount": 999999
}{
"ruleName": "FX Commission",
"transactionType": "FX",
"percentage": 0.025,
"minAmount": 0,
"maxAmount": 999999
}{
"ruleName": "Premium Transfer",
"transactionType": "TRANSFER",
"flatAmount": 5.00,
"percentage": 0.01,
"conditionsJson": "{\"customerTier\": \"premium\", \"region\": \"international\"}",
"minAmount": 100,
"maxAmount": 10000
}[
{
"ruleName": "Small Transaction Fee",
"transactionType": "PAYMENT",
"flatAmount": 1.00,
"minAmount": 0,
"maxAmount": 50
},
{
"ruleName": "Medium Transaction Fee",
"transactionType": "PAYMENT",
"flatAmount": 2.00,
"minAmount": 50.01,
"maxAmount": 500
},
{
"ruleName": "Large Transaction Fee",
"transactionType": "PAYMENT",
"flatAmount": 5.00,
"minAmount": 500.01,
"maxAmount": 999999
}
]curl http://localhost:5001/health# Database
ConnectionStrings__Default=Host=localhost;Database=;Username=;Password=
# Logging
Logging__LogLevel__Default=Information{
"ConnectionStrings": {
"Default": "Host=localhost;Database=feeengine;Username=;Password="
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}The service includes:
- Structured logging with Microsoft.Extensions.Logging
- Health checks endpoint (
/health) - Swagger/OpenAPI documentation
- Request/response logging middleware
# Build image
docker build -t feeengine:latest -f src/FeeEngine.Api/Dockerfile .
# Run container
docker run -p 5001:8080 \
-e ConnectionStrings__Default="Host=db;Database=feeengine;Username=feeuser;Password=P@ssword123" \
feeengine:latesthelm install feeengine ./charts/feeengine \
--set image.tag=latest \
--set postgresql.enabled=true- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
For issues and questions:
- Create an issue in the repository
- Email: support@feeengine.com
- Domain: Core business entities and logic
- Application: Use cases and business rules
- Infrastructure: External concerns (database, messaging)
- API: User interface (controllers, DTOs)
- Repository Pattern
- CQRS (Command Query Responsibility Segregation)
- Dependency Injection
- Factory Pattern
- Strategy Pattern (rule evaluation)
- Database indexes on frequently queried fields
- Async/await throughout
- Connection pooling
- Query optimization with EF Core
- JSON column for flexible conditions
Built with β€οΈ using .NET 9