php-commerce/Data/ValidationResult.php
Snider cd16c7474e feat(commerce): implement CouponService with 5 methods + DTOs (#858)
- create(code, type, value, maxUses, expiresAt) → Coupon
- validate(code, order) → ValidationResult
- apply(coupon, order) → Order (mutates line-item totals)
- expire(coupon) → void
- report() → array of redemption stats

Data/Coupon.php and Data/ValidationResult.php as readonly DTOs.
Pest unit tests with _Good/_Bad/_Ugly per AX-10 for all 5 methods.
pint/pest skipped (vendor binaries missing in sandbox).
Legacy helpers in CouponService preserved.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=858
2026-04-25 04:41:44 +01:00

82 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mod\Commerce\Data;
/**
* Coupon validation result for the RFC CouponService API.
*/
readonly class ValidationResult
{
public function __construct(
public bool $valid,
public ?string $reason,
public float $discountAmount,
public string $discountType,
public ?Coupon $coupon = null,
) {}
public static function valid(Coupon $coupon, float $discountAmount, string $discountType): self
{
return new self(
valid: true,
reason: null,
discountAmount: round($discountAmount, 2),
discountType: $discountType,
coupon: $coupon,
);
}
public static function invalid(
string $reason,
string $discountType = 'none',
?Coupon $coupon = null,
): self {
return new self(
valid: false,
reason: $reason,
discountAmount: 0.0,
discountType: $discountType,
coupon: $coupon,
);
}
public function isValid(): bool
{
return $this->valid;
}
public function getMessage(): ?string
{
return $this->reason;
}
public function getCoupon(): ?Coupon
{
return $this->coupon;
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'valid' => $this->valid,
'reason' => $this->reason,
'discount_amount' => $this->discountAmount,
'discount_type' => $this->discountType,
'coupon' => $this->coupon?->toArray(),
];
}
public function __get(string $name): mixed
{
return match ($name) {
'discount_amount' => $this->discountAmount,
'discount_type' => $this->discountType,
default => null,
};
}
}