/** * Domain Entity: Transaction * * Represents a financial transaction in the league wallet system. */ import { RacingDomainInvariantError, RacingDomainValidationError } from '../../errors/RacingDomainError'; import { Entity } from '@core/shared/domain/Entity'; import type { Money } from '../../value-objects/Money'; import { LeagueWalletId } from './LeagueWalletId'; import { TransactionId } from './TransactionId'; export type TransactionType = | 'sponsorship_payment' | 'membership_payment' | 'prize_payout' | 'withdrawal' | 'refund'; export type TransactionStatus = 'pending' | 'completed' | 'failed' | 'cancelled'; export interface TransactionProps { id: TransactionId; walletId: LeagueWalletId; type: TransactionType; amount: Money; platformFee: Money; netAmount: Money; status: TransactionStatus; createdAt: Date; completedAt: Date | undefined; description: string | undefined; metadata: Record | undefined; } export class Transaction extends Entity { readonly walletId: LeagueWalletId; 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) { super(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', }); } static rehydrate(props: { id: string; walletId: string; type: TransactionType; amount: Money; platformFee: Money; netAmount: Money; status: TransactionStatus; createdAt: Date; completedAt?: Date; description?: string; metadata?: Record; }): Transaction { return new Transaction({ id: TransactionId.create(props.id), walletId: LeagueWalletId.create(props.walletId), type: props.type, amount: props.amount, platformFee: props.platformFee, netAmount: props.netAmount, status: props.status, createdAt: props.createdAt, completedAt: props.completedAt, description: props.description, metadata: props.metadata, }); } private static validate(props: Omit): void { if (!props.id) { throw new RacingDomainValidationError('Transaction ID is required'); } if (!props.walletId) { 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'; } }