/** * Value Object: Money * Represents a monetary amount with currency and platform fee calculation */ import type { ValueObject } from '@core/shared/domain/ValueObject'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; export type Currency = 'USD' | 'EUR' | 'GBP'; export const isCurrency = (value: string): value is Currency => value === 'USD' || value === 'EUR' || value === 'GBP'; export interface MoneyProps { amount: number; currency: Currency; } export class Money implements ValueObject { static readonly DEFAULT_PLATFORM_FEE_PERCENTAGE = 0.10; readonly amount: number; readonly currency: Currency; private constructor(amount: number, currency: Currency) { this.amount = amount; this.currency = currency; } static create(amount: number, currency: Currency = 'USD'): Money { if (amount < 0) { throw new RacingDomainValidationError('Money amount cannot be negative'); } if (!Number.isFinite(amount)) { throw new RacingDomainValidationError('Money amount must be a finite number'); } return new Money(amount, currency); } /** * Calculate a fee amount for a given percentage. * Defaults to the current platform fee percentage. */ calculatePlatformFee(platformFeePercentage: number = Money.DEFAULT_PLATFORM_FEE_PERCENTAGE): Money { if (!Number.isFinite(platformFeePercentage) || platformFeePercentage < 0) { throw new RacingDomainValidationError('Platform fee percentage must be a non-negative finite number'); } const feeAmount = this.amount * platformFeePercentage; return new Money(feeAmount, this.currency); } /** * Calculate net amount after subtracting a fee. * Defaults to subtracting the current platform fee percentage. */ calculateNetAmount(platformFeePercentage: number = Money.DEFAULT_PLATFORM_FEE_PERCENTAGE): Money { const platformFee = this.calculatePlatformFee(platformFeePercentage); return new Money(this.amount - platformFee.amount, this.currency); } /** * Add two money amounts */ add(other: Money): Money { if (this.currency !== other.currency) { throw new RacingDomainValidationError('Cannot add money with different currencies'); } return new Money(this.amount + other.amount, this.currency); } /** * Subtract two money amounts */ subtract(other: Money): Money { if (this.currency !== other.currency) { throw new RacingDomainValidationError('Cannot subtract money with different currencies'); } const result = this.amount - other.amount; if (result < 0) { throw new RacingDomainValidationError('Subtraction would result in negative amount'); } return new Money(result, this.currency); } /** * Check if this money is greater than another */ isGreaterThan(other: Money): boolean { if (this.currency !== other.currency) { throw new RacingDomainValidationError('Cannot compare money with different currencies'); } return this.amount > other.amount; } get props(): MoneyProps { return { amount: this.amount, currency: this.currency, }; } /** * Check if this money equals another */ equals(other: ValueObject): boolean { const a = this.props; const b = other.props; return a.amount === b.amount && a.currency === b.currency; } }