100)) { throw new \DomainException( 'Percentage rate must be between 0 and 100.' ); } if ($type === DiscountType::AMOUNT && $amount === null) { throw new \DomainException( 'Amount must be provided for amount discounts.' ); } if ($amount !== null && $amount->isNegative()) { throw new \DomainException( 'Discount amount cannot be negative.' ); } } public static function fromAmount(Money $amount): self { return new self(type: DiscountType::AMOUNT, amount: $amount); } public static function fromPercentage(int $percentageRate): self { return new self(type: DiscountType::PERCENT, percentage: $percentageRate); } public function value(): Money | int { if ($this->type === DiscountType::AMOUNT) { return $this->getAmount(); } return $this->getPercentage(); } public function isPercentage(): bool { return $this->type === DiscountType::PERCENT; } public function getPercentageRate(): float { return $this->getPercentage() / 100; } public function getPercentage(): int { if ($this->type !== DiscountType::PERCENT) { throw new \DomainException( 'Cannot get percentage from a non-percentage discount.' ); } if ($this->percentage === null) { throw new \DomainException( 'Percentage rate must be set for percentage discounts.' ); } return $this->percentage; } public function getAmount(): Money { if ($this->type !== DiscountType::AMOUNT) { throw new \DomainException( 'Cannot get amount from a non-amount discount.' ); } if ($this->amount === null) { throw new \DomainException( 'Amount must be set for amount discounts.' ); } return $this->amount; } public function calculateDiscountAmount(Money $basePrice): Money { if ($this->type === DiscountType::AMOUNT) { $amount = $this->getAmount(); if (! $amount->getCurrency()->equals($basePrice->getCurrency())) { throw new \DomainException('Currency mismatch.'); } return $amount; } $rate = $this->getPercentageRate(); return $basePrice->multiply((string) $rate, Product::ROUNDING); } }