/** * Domain Entity: Transaction * * Represents a financial transaction in the league wallet system. */ import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError'; import type { Money } from '../value-objects/Money'; import type { IEntity } from '@gridpilot/shared/domain'; export type TransactionType = | 'sponsorship_payment' | 'membership_payment' | 'prize_payout' | 'withdrawal' | 'refund'; export type TransactionStatus = 'pending' | 'completed' | 'failed' | 'cancelled'; export interface TransactionProps { id: string; walletId: string; type: TransactionType; amount: Money; platformFee: Money; netAmount: Money; status: TransactionStatus; createdAt: Date; completedAt: Date | undefined; description: string | undefined; metadata: Record | undefined; } export class Transaction implements IEntity { readonly id: string; readonly walletId: string; readonly type: TransactionType; readonly amount: Money; readonly platformFee: Money; readonly netAmount: Money; readonly status: TransactionStatus; readonly createdAt: Date; readonly completedAt: Date | undefined; readonly description: string | undefined; readonly metadata: Record | undefined; private constructor(props: TransactionProps) { this.id = props.id; this.walletId = props.walletId; this.type = props.type; this.amount = props.amount; this.platformFee = props.platformFee; this.netAmount = props.netAmount; this.status = props.status; this.createdAt = props.createdAt; this.completedAt = props.completedAt; this.description = props.description; this.metadata = props.metadata; } static create(props: Omit & { createdAt?: Date; status?: TransactionStatus; }): Transaction { this.validate(props); const platformFee = props.amount.calculatePlatformFee(); const netAmount = props.amount.calculateNetAmount(); return new Transaction({ ...props, platformFee, netAmount, createdAt: props.createdAt ?? new Date(), status: props.status ?? 'pending', }); } private static validate(props: Omit): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('Transaction ID is required'); } if (!props.walletId || props.walletId.trim().length === 0) { throw new RacingDomainValidationError('Transaction walletId is required'); } if (!props.type) { throw new RacingDomainValidationError('Transaction type is required'); } if (!props.amount) { throw new RacingDomainValidationError('Transaction amount is required'); } if (props.amount.amount <= 0) { throw new RacingDomainValidationError('Transaction amount must be greater than zero'); } } /** * Mark transaction as completed */ complete(): Transaction { if (this.status === 'completed') { throw new RacingDomainInvariantError('Transaction is already completed'); } if (this.status === 'failed' || this.status === 'cancelled') { throw new RacingDomainInvariantError('Cannot complete a failed or cancelled transaction'); } return new Transaction({ ...this, status: 'completed', completedAt: new Date(), }); } /** * Mark transaction as failed */ fail(): Transaction { if (this.status === 'completed') { throw new RacingDomainInvariantError('Cannot fail a completed transaction'); } return new Transaction({ ...this, status: 'failed', }); } /** * Cancel transaction */ cancel(): Transaction { if (this.status === 'completed') { throw new RacingDomainInvariantError('Cannot cancel a completed transaction'); } return new Transaction({ ...this, status: 'cancelled', }); } /** * Check if transaction is completed */ isCompleted(): boolean { return this.status === 'completed'; } /** * Check if transaction is pending */ isPending(): boolean { return this.status === 'pending'; } }