Files
gridpilot.gg/packages/racing/domain/entities/Transaction.ts
2025-12-12 14:23:40 +01:00

162 lines
4.2 KiB
TypeScript

/**
* 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<string, unknown> | undefined;
}
export class Transaction implements IEntity<string> {
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<string, unknown> | 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<TransactionProps, 'createdAt' | 'status' | 'platformFee' | 'netAmount'> & {
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<TransactionProps, 'createdAt' | 'status' | 'platformFee' | 'netAmount'>): 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';
}
}