diff --git a/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityChecker.php b/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityChecker.php new file mode 100644 index 000000000000..affaaf3bb62d --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityChecker.php @@ -0,0 +1,57 @@ +promotionChecker = $promotionChecker; + $this->promotionCouponChecker = $promotionCouponChecker; + } + + public function isEligible(PromotionCouponInterface $promotionCoupon, OrderInterface $cart): bool + { + /** @var PromotionInterface $promotion */ + $promotion = $promotionCoupon->getPromotion(); + + if (!$promotion->getChannels()->contains($cart->getChannel())) { + return false; + } + + if (!$this->promotionCouponChecker->isEligible($cart, $promotionCoupon)) { + return false; + } + + if (!$this->promotionChecker->isEligible($cart, $promotionCoupon->getPromotion())) { + return false; + } + + return true; + } +} diff --git a/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityCheckerInterface.php b/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityCheckerInterface.php new file mode 100644 index 000000000000..df98e7d33841 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/Checker/AppliedCouponEligibilityCheckerInterface.php @@ -0,0 +1,13 @@ + + + + + + - diff --git a/src/Sylius/Bundle/ApiBundle/Validator/Constraints/PromotionCouponEligibilityValidator.php b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/PromotionCouponEligibilityValidator.php index 187d1a4aaed7..7a77f47969e5 100644 --- a/src/Sylius/Bundle/ApiBundle/Validator/Constraints/PromotionCouponEligibilityValidator.php +++ b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/PromotionCouponEligibilityValidator.php @@ -13,13 +13,11 @@ namespace Sylius\Bundle\ApiBundle\Validator\Constraints; +use Sylius\Bundle\ApiBundle\Checker\AppliedCouponEligibilityCheckerInterface; use Sylius\Bundle\ApiBundle\Command\Cart\ApplyCouponToCart; use Sylius\Component\Core\Model\OrderInterface; -use Sylius\Component\Core\Model\PromotionInterface; +use Sylius\Component\Core\Model\PromotionCouponInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; -use Sylius\Component\Promotion\Checker\Eligibility\PromotionCouponEligibilityCheckerInterface; -use Sylius\Component\Promotion\Checker\Eligibility\PromotionEligibilityCheckerInterface; -use Sylius\Component\Promotion\Model\PromotionCouponInterface; use Sylius\Component\Promotion\Repository\PromotionCouponRepositoryInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -34,22 +32,17 @@ final class PromotionCouponEligibilityValidator extends ConstraintValidator /** @var OrderRepositoryInterface */ private $orderRepository; - /** @var PromotionEligibilityCheckerInterface */ - private $promotionChecker; - - /** @var PromotionCouponEligibilityCheckerInterface */ - private $promotionCouponChecker; + /** @var AppliedCouponEligibilityCheckerInterface */ + private $appliedCouponEligibilityChecker; public function __construct( PromotionCouponRepositoryInterface $promotionCouponRepository, OrderRepositoryInterface $orderRepository, - PromotionEligibilityCheckerInterface $promotionChecker, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker + AppliedCouponEligibilityCheckerInterface $appliedCouponEligibilityChecker ) { $this->promotionCouponRepository = $promotionCouponRepository; $this->orderRepository = $orderRepository; - $this->promotionChecker = $promotionChecker; - $this->promotionCouponChecker = $promotionCouponChecker; + $this->appliedCouponEligibilityChecker = $appliedCouponEligibilityChecker; } public function validate($value, Constraint $constraint): void @@ -62,9 +55,6 @@ public function validate($value, Constraint $constraint): void /** @var PromotionCouponInterface|null $promotionCoupon */ $promotionCoupon = $this->promotionCouponRepository->findOneBy(['code' => $value->couponCode]); - /** @var PromotionInterface $promotion */ - $promotion = $promotionCoupon->getPromotion(); - /** @var OrderInterface $cart */ $cart = $this->orderRepository->findCartByTokenValue($value->getOrderTokenValue()); @@ -72,16 +62,13 @@ public function validate($value, Constraint $constraint): void if ( $promotionCoupon === null || - !$this->promotionCouponChecker->isEligible($cart, $promotionCoupon) || - !$this->promotionChecker->isEligible($cart, $promotionCoupon->getPromotion()) || - !$promotion->getChannels()->contains($cart->getChannel()) + !$this->appliedCouponEligibilityChecker->isEligible($promotionCoupon, $cart) ) { - $this->addViolation('sylius.promotion_coupon.is_invalid', 'couponCode'); + $this->context + ->buildViolation('sylius.promotion_coupon.is_invalid') + ->atPath('couponCode') + ->addViolation() + ; } } - - private function addViolation(string $message, string $path): void - { - $this->context->buildViolation($message)->atPath($path)->addViolation(); - } } diff --git a/src/Sylius/Bundle/ApiBundle/spec/Checker/AppliedCouponEligibilityCheckerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Checker/AppliedCouponEligibilityCheckerSpec.php new file mode 100644 index 000000000000..b0399ff06492 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/Checker/AppliedCouponEligibilityCheckerSpec.php @@ -0,0 +1,133 @@ +beConstructedWith($promotionChecker, $promotionCouponChecker); + } + + function it_implements_promotion_coupon_eligibility_checker_interface(): void + { + $this->shouldImplement(AppliedCouponEligibilityCheckerInterface::class); + } + + function it_returns_false_if_cart_channel_is_not_one_of_promotion_channels( + PromotionEligibilityCheckerInterface $promotionChecker, + PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, + PromotionCouponInterface $promotionCoupon, + PromotionInterface $promotion, + OrderInterface $cart, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel, + ChannelInterface $thirdChannel + ): void { + $promotionCoupon->getPromotion()->willReturn($promotion); + + $promotion->getChannels()->willReturn(new ArrayCollection([ + $secondChannel->getWrappedObject(), + $thirdChannel->getWrappedObject() + ])); + $cart->getChannel()->willReturn($firstChannel); + + $promotionChecker->isEligible(Argument::any())->shouldNotBeCalled(); + $promotionCouponChecker->isEligible(Argument::any())->shouldNotBeCalled(); + + $this->isEligible($promotionCoupon, $cart)->shouldReturn(false); + } + + function it_returns_false_if_coupon_is_not_eligible( + PromotionEligibilityCheckerInterface $promotionChecker, + PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, + PromotionCouponInterface $promotionCoupon, + PromotionInterface $promotion, + OrderInterface $cart, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel + ): void { + $promotionCoupon->getPromotion()->willReturn($promotion); + + $promotion->getChannels()->willReturn(new ArrayCollection([ + $firstChannel->getWrappedObject(), + $secondChannel->getWrappedObject() + ])); + $cart->getChannel()->willReturn($firstChannel); + + $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(false); + $promotionChecker->isEligible(Argument::any())->shouldNotBeCalled(); + + $this->isEligible($promotionCoupon, $cart)->shouldReturn(false); + } + + function it_returns_false_if_promotion_is_not_eligible( + PromotionEligibilityCheckerInterface $promotionChecker, + PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, + PromotionCouponInterface $promotionCoupon, + PromotionInterface $promotion, + OrderInterface $cart, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel + ): void { + $promotionCoupon->getPromotion()->willReturn($promotion); + + $promotion->getChannels()->willReturn(new ArrayCollection([ + $firstChannel->getWrappedObject(), + $secondChannel->getWrappedObject() + ])); + $cart->getChannel()->willReturn($firstChannel); + + $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(true); + $promotionChecker->isEligible($cart, $promotion)->willReturn(false); + + $this->isEligible($promotionCoupon, $cart)->shouldReturn(false); + } + + function it_returns_true_if_promotion_and_coupon_are_eligible( + PromotionEligibilityCheckerInterface $promotionChecker, + PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, + PromotionCouponInterface $promotionCoupon, + PromotionInterface $promotion, + OrderInterface $cart, + ChannelInterface $firstChannel, + ChannelInterface $secondChannel + ): void { + $promotionCoupon->getPromotion()->willReturn($promotion); + + $promotion->getChannels()->willReturn(new ArrayCollection([ + $firstChannel->getWrappedObject(), + $secondChannel->getWrappedObject() + ])); + $cart->getChannel()->willReturn($firstChannel); + + $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(true); + $promotionChecker->isEligible($cart, $promotion)->willReturn(true); + + $this->isEligible($promotionCoupon, $cart)->shouldReturn(true); + } +} diff --git a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/PromotionCouponEligibilityValidatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/PromotionCouponEligibilityValidatorSpec.php index 83bcfb04a4ad..ea3e8487c1b6 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/PromotionCouponEligibilityValidatorSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/PromotionCouponEligibilityValidatorSpec.php @@ -13,17 +13,13 @@ namespace spec\Sylius\Bundle\ApiBundle\Validator\Constraints; -use Doctrine\Common\Collections\ArrayCollection; use PhpSpec\ObjectBehavior; +use Sylius\Bundle\ApiBundle\Checker\AppliedCouponEligibilityCheckerInterface; use Sylius\Bundle\ApiBundle\Command\Cart\ApplyCouponToCart; use Sylius\Bundle\ApiBundle\Validator\Constraints\PromotionCouponEligibility; -use Sylius\Component\Core\Model\ChannelInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PromotionCouponInterface; -use Sylius\Component\Core\Model\PromotionInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; -use Sylius\Component\Promotion\Checker\Eligibility\PromotionCouponEligibilityCheckerInterface; -use Sylius\Component\Promotion\Checker\Eligibility\PromotionEligibilityCheckerInterface; use Sylius\Component\Promotion\Repository\PromotionCouponRepositoryInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidatorInterface; @@ -35,14 +31,12 @@ final class PromotionCouponEligibilityValidatorSpec extends ObjectBehavior function let( PromotionCouponRepositoryInterface $promotionCouponRepository, OrderRepositoryInterface $orderRepository, - PromotionEligibilityCheckerInterface $promotionChecker, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker + AppliedCouponEligibilityCheckerInterface $appliedCouponEligibilityChecker ): void { $this->beConstructedWith( $promotionCouponRepository, $orderRepository, - $promotionChecker, - $promotionCouponChecker + $appliedCouponEligibilityChecker ); } @@ -61,15 +55,11 @@ function it_throws_an_exception_if_constraint_is_not_of_expected_type(): void function it_does_not_add_violation_if_promotion_coupon_is_eligible( PromotionCouponRepositoryInterface $promotionCouponRepository, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, - PromotionEligibilityCheckerInterface $promotionChecker, + AppliedCouponEligibilityCheckerInterface $appliedCouponEligibilityChecker, PromotionCouponInterface $promotionCoupon, - PromotionInterface $promotion, OrderRepositoryInterface $orderRepository, OrderInterface $cart, - ExecutionContextInterface $executionContext, - ChannelInterface $firstChannel, - ChannelInterface $secondChannel + ExecutionContextInterface $executionContext ): void { $this->initialize($executionContext); $constraint = new PromotionCouponEligibility(); @@ -78,18 +68,11 @@ function it_does_not_add_violation_if_promotion_coupon_is_eligible( $value->setOrderTokenValue('token'); $promotionCouponRepository->findOneBy(['code' => 'couponCode'])->willReturn($promotionCoupon); - $orderRepository->findCartByTokenValue('token')->willReturn($cart); - $cart->getChannel()->willReturn($firstChannel); $cart->setPromotionCoupon($promotionCoupon)->shouldBeCalled(); - $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(true); - - $promotionCoupon->getPromotion()->willReturn($promotion); - $promotion->getChannels()->willReturn(new ArrayCollection([$firstChannel->getWrappedObject(), $secondChannel->getWrappedObject()])); - - $promotionChecker->isEligible($cart, $promotion)->willReturn(true); + $appliedCouponEligibilityChecker->isEligible($promotionCoupon, $cart)->willReturn(true); $executionContext->buildViolation('sylius.promotion_coupon.is_invalid')->shouldNotBeCalled(); @@ -98,80 +81,8 @@ function it_does_not_add_violation_if_promotion_coupon_is_eligible( function it_adds_violation_if_promotion_coupon_is_not_eligible( PromotionCouponRepositoryInterface $promotionCouponRepository, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, - PromotionCouponInterface $promotionCoupon, - OrderRepositoryInterface $orderRepository, - OrderInterface $cart, - ExecutionContextInterface $executionContext, - ConstraintViolationBuilderInterface $constraintViolationBuilder - ): void { - $this->initialize($executionContext); - $constraint = new PromotionCouponEligibility(); - - $value = new ApplyCouponToCart('couponCode'); - $value->setOrderTokenValue('token'); - - $promotionCouponRepository->findOneBy(['code' => 'couponCode'])->willReturn($promotionCoupon); - - $orderRepository->findCartByTokenValue('token')->willReturn($cart); - - $cart->setPromotionCoupon($promotionCoupon)->shouldBeCalled(); - - $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(false); - - $executionContext->buildViolation('sylius.promotion_coupon.is_invalid')->willReturn($constraintViolationBuilder); - $constraintViolationBuilder->atPath('couponCode')->willReturn($constraintViolationBuilder); - $constraintViolationBuilder->addViolation()->shouldBeCalled(); - - $this->validate($value, $constraint); - } - - function it_adds_violation_if_promotion_is_not_available_in_cart_channel( - PromotionCouponRepositoryInterface $promotionCouponRepository, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, - PromotionEligibilityCheckerInterface $promotionChecker, - PromotionCouponInterface $promotionCoupon, - PromotionInterface $promotion, - OrderRepositoryInterface $orderRepository, - OrderInterface $cart, - ExecutionContextInterface $executionContext, - ConstraintViolationBuilderInterface $constraintViolationBuilder, - ChannelInterface $firstChannel, - ChannelInterface $secondChannel, - ChannelInterface $thirdChannel - ): void { - $this->initialize($executionContext); - $constraint = new PromotionCouponEligibility(); - - $value = new ApplyCouponToCart('couponCode'); - $value->setOrderTokenValue('token'); - - $promotionCouponRepository->findOneBy(['code' => 'couponCode'])->willReturn($promotionCoupon); - $promotionCoupon->getPromotion()->willReturn($promotion); - $promotion->getChannels()->willReturn(new ArrayCollection([$firstChannel->getWrappedObject(), $secondChannel->getWrappedObject()])); - - $orderRepository->findCartByTokenValue('token')->willReturn($cart); - $cart->getChannel()->willReturn($thirdChannel); - - $cart->setPromotionCoupon($promotionCoupon)->shouldBeCalled(); - - $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(true); - - $promotionChecker->isEligible($cart, $promotion)->willReturn(false); - - $executionContext->buildViolation('sylius.promotion_coupon.is_invalid')->willReturn($constraintViolationBuilder); - $constraintViolationBuilder->atPath('couponCode')->willReturn($constraintViolationBuilder); - $constraintViolationBuilder->addViolation()->shouldBeCalled(); - - $this->validate($value, $constraint); - } - - function it_adds_violation_if_promotion_is_not_eligible( - PromotionCouponRepositoryInterface $promotionCouponRepository, - PromotionCouponEligibilityCheckerInterface $promotionCouponChecker, - PromotionEligibilityCheckerInterface $promotionChecker, + AppliedCouponEligibilityCheckerInterface $appliedCouponEligibilityChecker, PromotionCouponInterface $promotionCoupon, - PromotionInterface $promotion, OrderRepositoryInterface $orderRepository, OrderInterface $cart, ExecutionContextInterface $executionContext, @@ -184,15 +95,11 @@ function it_adds_violation_if_promotion_is_not_eligible( $value->setOrderTokenValue('token'); $promotionCouponRepository->findOneBy(['code' => 'couponCode'])->willReturn($promotionCoupon); - $orderRepository->findCartByTokenValue('token')->willReturn($cart); $cart->setPromotionCoupon($promotionCoupon)->shouldBeCalled(); - $promotionCouponChecker->isEligible($cart, $promotionCoupon)->willReturn(true); - - $promotionCoupon->getPromotion()->willReturn($promotion); - $promotionChecker->isEligible($cart, $promotion)->willReturn(false); + $appliedCouponEligibilityChecker->isEligible($promotionCoupon, $cart)->willReturn(false); $executionContext->buildViolation('sylius.promotion_coupon.is_invalid')->willReturn($constraintViolationBuilder); $constraintViolationBuilder->atPath('couponCode')->willReturn($constraintViolationBuilder);