/** * Value Object: Money * Represents a monetary amount with currency and platform fee calculation */ import { RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IValueObject } from '@gridpilot/shared/domain'; export type Currency = 'USD' | 'EUR' | 'GBP'; export interface MoneyProps { amount: number; currency: Currency; } export class Money implements IValueObject { private static readonly 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 platform fee (10%) */ calculatePlatformFee(): Money { const feeAmount = this.amount * Money.PLATFORM_FEE_PERCENTAGE; return new Money(feeAmount, this.currency); } /** * Calculate net amount after platform fee */ calculateNetAmount(): Money { const platformFee = this.calculatePlatformFee(); 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: IValueObject): boolean { const a = this.props; const b = other.props; return a.amount === b.amount && a.currency === b.currency; } /** * Format money for display */ format(): string { const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: this.currency, minimumFractionDigits: 2, maximumFractionDigits: 2, }); return formatter.format(this.amount); } }