diff --git a/src/transactions/transaction-fees.service.spec.ts b/src/transactions/transaction-fees.service.spec.ts new file mode 100644 index 0000000..a654b96 --- /dev/null +++ b/src/transactions/transaction-fees.service.spec.ts @@ -0,0 +1,48 @@ +import { TransactionFeesService } from './transaction-fees.service'; + +describe('TransactionFeesService', () => { + let service: TransactionFeesService; + + beforeEach(() => { + service = new TransactionFeesService(); + }); + + it('calculates fees correctly for a standard amount', () => { + const result = service.calculateFees(100_000); + + expect(result.transactionAmount).toBe(100_000); + expect(result.platformFee).toBe(1_500); // 1.5% + expect(result.platformFeeRate).toBe(0.015); + expect(result.agentCommission).toBe(3_000); // 3% + expect(result.agentCommissionRate).toBe(0.03); + // tax = (100000 + 1500 + 3000) * 0.08 = 8360 + expect(result.tax).toBe(8_360); + expect(result.taxRate).toBe(0.08); + expect(result.totalFees).toBe(12_860); + expect(result.totalAmount).toBe(112_860); + }); + + it('uses custom agent commission rate when provided', () => { + const result = service.calculateFees(100_000, 0.05); + + expect(result.agentCommission).toBe(5_000); + expect(result.agentCommissionRate).toBe(0.05); + }); + + it('returns zero fees for zero amount', () => { + const result = service.calculateFees(0); + + expect(result.platformFee).toBe(0); + expect(result.agentCommission).toBe(0); + expect(result.tax).toBe(0); + expect(result.totalFees).toBe(0); + expect(result.totalAmount).toBe(0); + }); + + it('rounds to 2 decimal places', () => { + const result = service.calculateFees(333.33); + + expect(result.platformFee).toBe(5); // 333.33 * 0.015 = 4.99995 → 5 + expect(Number.isInteger(result.platformFee * 100)).toBe(true); + }); +}); diff --git a/src/transactions/transaction-fees.service.ts b/src/transactions/transaction-fees.service.ts new file mode 100644 index 0000000..8eba23a --- /dev/null +++ b/src/transactions/transaction-fees.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { FeeBreakdown } from './dto/transaction.dto'; + +/** Default fee rates */ +const PLATFORM_FEE_RATE = 0.015; // 1.5% +const DEFAULT_AGENT_COMMISSION_RATE = 0.03; // 3% +const TAX_RATE = 0.08; // 8% + +@Injectable() +export class TransactionFeesService { + /** + * Calculate all fees for a given transaction amount. + * @param amount - The base transaction amount + * @param agentCommissionRate - Optional override for agent commission rate (0–1) + */ + calculateFees(amount: number, agentCommissionRate?: number): FeeBreakdown { + const commissionRate = agentCommissionRate ?? DEFAULT_AGENT_COMMISSION_RATE; + + const platformFee = this.round(amount * PLATFORM_FEE_RATE); + const agentCommission = this.round(amount * commissionRate); + const taxableAmount = amount + platformFee + agentCommission; + const tax = this.round(taxableAmount * TAX_RATE); + const totalFees = this.round(platformFee + agentCommission + tax); + const totalAmount = this.round(amount + totalFees); + + return { + transactionAmount: amount, + platformFee, + platformFeeRate: PLATFORM_FEE_RATE, + agentCommission, + agentCommissionRate: commissionRate, + tax, + taxRate: TAX_RATE, + totalFees, + totalAmount, + }; + } + + private round(value: number): number { + return Math.round(value * 100) / 100; + } +}