diff --git a/src/Services/CouponResult.php b/src/Services/CouponResult.php new file mode 100644 index 0000000..21660f0 --- /dev/null +++ b/src/Services/CouponResult.php @@ -0,0 +1,50 @@ +status === self::valid; + } + + public function message(): string + { + return match ($this->status) { + self::valid => 'Coupon applied.', + self::notFound => 'That coupon code was not recognised.', + self::inactive => 'That coupon is no longer active.', + self::notStarted => 'That coupon is not valid yet.', + self::expired => 'That coupon has expired.', + self::exhausted => 'That coupon has reached its redemption limit.', + self::belowMinimum => 'Your cart does not meet the minimum for this coupon.', + default => 'That coupon could not be applied.', + }; + } +} diff --git a/src/Services/CouponService.php b/src/Services/CouponService.php new file mode 100644 index 0000000..f099635 --- /dev/null +++ b/src/Services/CouponService.php @@ -0,0 +1,54 @@ +coupons->findByCode($code); + if ($coupon === null) { + return CouponResult::fail(CouponResult::notFound); + } + + $now ??= gmdate('c'); + + if ((int) $coupon['isActive'] !== 1) { + return CouponResult::fail(CouponResult::inactive, $coupon); + } + if ($coupon['startsAt'] !== null && $coupon['startsAt'] !== '' && $now < $coupon['startsAt']) { + return CouponResult::fail(CouponResult::notStarted, $coupon); + } + if ($coupon['expiresAt'] !== null && $coupon['expiresAt'] !== '' && $now > $coupon['expiresAt']) { + return CouponResult::fail(CouponResult::expired, $coupon); + } + if ($coupon['maxRedemptions'] !== null && (int) $coupon['redeemedCount'] >= (int) $coupon['maxRedemptions']) { + return CouponResult::fail(CouponResult::exhausted, $coupon); + } + if ($subtotalCents < (int) $coupon['minSubtotalCents']) { + return CouponResult::fail(CouponResult::belowMinimum, $coupon); + } + + return CouponResult::ok($coupon, $this->discountFor($coupon, $subtotalCents)); + } + + public function discountFor(array $coupon, int $subtotalCents): int + { + $discount = $coupon['type'] === 'percent' + ? (int) floor($subtotalCents * (int) $coupon['value'] / 100) + : (int) $coupon['value']; + + return max(0, min($discount, $subtotalCents)); + } + + public function redeem(int $couponId): void + { + $this->coupons->recordRedemption($couponId); + } +}