diff --git a/features/being_unable_to_reorder_the_order_placed_by_another_customer.feature b/features/being_unable_to_reorder_the_order_placed_by_another_customer.feature
new file mode 100644
index 0000000..3501d71
--- /dev/null
+++ b/features/being_unable_to_reorder_the_order_placed_by_another_customer.feature
@@ -0,0 +1,21 @@
+@reordering
+Feature: Being unable to reorder the order placed by another customer
+ In order to maintain shop security
+ As a Store Owner
+ I want Customer to be the only person allowed to reorder their previously placed order
+
+ Background:
+ Given the store operates on a single channel in "United States"
+ And the store has a product "Angel T-Shirt"
+ And the store ships everywhere for free
+ And the store allows paying with "Cash on Delivery"
+ And there is a customer "Rick Sanchez" identified by an email "rick.sanchez@wubba-lubba-dub-dub.com" and a password "Morty"
+ And there is a customer "Morty Smith" identified by an email "morty.smith@wubba-lubba-dub-dub.com" and a password "Rick"
+ And a customer "Morty Smith" placed an order "#00000666"
+ And the customer bought a single "Angel T-Shirt"
+ And the customer chose "Free" shipping method to "United States" with "Cash on Delivery" payment
+
+ @application
+ Scenario: Being unable to reorder the order placed by another customer
+ When the customer "rick.sanchez@wubba-lubba-dub-dub.com" tries to reorder the order "#00000666"
+ Then the order "#00000666" should not be reordered
diff --git a/spec/Checker/OrderCustomerRelationCheckerSpec.php b/spec/Checker/OrderCustomerRelationCheckerSpec.php
new file mode 100644
index 0000000..df1e624
--- /dev/null
+++ b/spec/Checker/OrderCustomerRelationCheckerSpec.php
@@ -0,0 +1,53 @@
+shouldImplement(OrderCustomerRelationCheckerInterface::class);
+ }
+
+ function it_returns_true_when_order_was_placed_by_customer(
+ CustomerInterface $orderCustomer,
+ CustomerInterface $customer,
+ OrderInterface $order
+ ): void {
+ $orderCustomer->getId()->willReturn(1);
+ $customer->getId()->willReturn(1);
+
+ $order->getCustomer()->willReturn($orderCustomer);
+
+ $this->wasOrderPlacedByCustomer($order, $customer)->shouldReturn(true);
+ }
+
+ function it_returns_false_when_order_was_not_placed_by_customer(
+ CustomerInterface $orderCustomer,
+ CustomerInterface $customer,
+ OrderInterface $order
+ ): void {
+ $orderCustomer->getId()->willReturn(1);
+ $customer->getId()->willReturn(2);
+
+ $order->getCustomer()->willReturn($orderCustomer);
+
+ $this->wasOrderPlacedByCustomer($order, $customer)->shouldReturn(false);
+ }
+
+ function it_returns_false_when_order_has_no_customer_assigned(
+ CustomerInterface $customer,
+ OrderInterface $order
+ ): void {
+ $order->getCustomer()->willReturn(null);
+
+ $this->wasOrderPlacedByCustomer($order, $customer)->shouldReturn(false);
+ }
+}
diff --git a/spec/Reorder/ReordererSpec.php b/spec/Reorder/ReordererSpec.php
index 9daedd6..511dac7 100644
--- a/spec/Reorder/ReordererSpec.php
+++ b/spec/Reorder/ReordererSpec.php
@@ -6,14 +6,17 @@
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
+use Nette\InvalidStateException;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\MoneyBundle\Formatter\MoneyFormatterInterface;
use Sylius\Component\Core\Model\ChannelInterface;
+use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Model\PromotionInterface;
use Sylius\Component\Order\Processor\OrderProcessorInterface;
+use Sylius\CustomerReorderPlugin\Checker\OrderCustomerRelationCheckerInterface;
use Sylius\CustomerReorderPlugin\Factory\OrderFactoryInterface;
use Sylius\CustomerReorderPlugin\Reorder\Reorderer;
use Sylius\CustomerReorderPlugin\Reorder\ReordererInterface;
@@ -32,7 +35,8 @@ function let(
MoneyFormatterInterface $moneyFormatter,
Session $session,
ReorderEligibilityChecker $reorderEligibilityChecker,
- ReorderEligibilityCheckerResponseProcessorInterface $reorderEligibilityCheckerResponseProcessor
+ ReorderEligibilityCheckerResponseProcessorInterface $reorderEligibilityCheckerResponseProcessor,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker
): void {
$this->beConstructedWith(
$orderFactory,
@@ -41,7 +45,8 @@ function let(
$moneyFormatter,
$session,
$reorderEligibilityChecker,
- $reorderEligibilityCheckerResponseProcessor
+ $reorderEligibilityCheckerResponseProcessor,
+ $orderCustomerRelationChecker
);
}
@@ -59,7 +64,9 @@ function it_creates_and_persists_reorder_from_existing_order(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
ReorderEligibilityChecker $reorderEligibilityChecker,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker,
ChannelInterface $channel,
+ CustomerInterface $customer,
OrderInterface $order,
OrderInterface $reorder,
OrderItemInterface $firstOrderItem,
@@ -68,6 +75,8 @@ function it_creates_and_persists_reorder_from_existing_order(
$order->getTotal()->willReturn(100);
$order->getCurrencyCode()->willReturn('USD');
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $customer)->willReturn(true);
+
$reorder->getTotal()->willReturn(100);
$orderFactory->createFromExistingOrder($order, $channel)->willReturn($reorder);
@@ -81,14 +90,16 @@ function it_creates_and_persists_reorder_from_existing_order(
$secondOrderItem->getWrappedObject()
]));
- $this->reorder($order, $channel);
+ $this->reorder($order, $channel, $customer);
}
function it_checks_if_orders_totals_differ(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
ReorderEligibilityChecker $reorderEligibilityChecker,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker,
ChannelInterface $channel,
+ CustomerInterface $customer,
OrderInterface $order,
OrderInterface $reorder,
MoneyFormatterInterface $moneyFormatter,
@@ -101,6 +112,8 @@ function it_checks_if_orders_totals_differ(
$order->getCurrencyCode()->willReturn('USD');
$order->getPromotions()->willReturn($promotions);
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $customer)->willReturn(true);
+
$reorder->getTotal()->willReturn(150);
$reorder->getPromotions()->willReturn($promotions);
@@ -122,14 +135,16 @@ function it_checks_if_orders_totals_differ(
$entityManager->persist($reorder)->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
- $this->reorder($order, $channel);
+ $this->reorder($order, $channel, $customer);
}
function it_checks_if_promotion_is_no_longer_available(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
ReorderEligibilityChecker $reorderEligibilityChecker,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker,
ChannelInterface $channel,
+ CustomerInterface $customer,
OrderInterface $order,
OrderInterface $reorder,
MoneyFormatterInterface $moneyFormatter,
@@ -144,6 +159,8 @@ function it_checks_if_promotion_is_no_longer_available(
$secondPromotion->getWrappedObject()
]));
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $customer)->willReturn(true);
+
$firstPromotion->getName()->willReturn('test_promotion_01');
$secondPromotion->getName()->willReturn('test_promotion_02');
@@ -169,14 +186,16 @@ function it_checks_if_promotion_is_no_longer_available(
$entityManager->persist($reorder)->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
- $this->reorder($order, $channel);
+ $this->reorder($order, $channel, $customer);
}
function it_checks_if_price_of_any_item_has_changed(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
ReorderEligibilityChecker $reorderEligibilityChecker,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker,
ChannelInterface $channel,
+ CustomerInterface $customer,
OrderInterface $order,
OrderInterface $reorder,
OrderItemInterface $firstOrderItem,
@@ -194,6 +213,8 @@ function it_checks_if_price_of_any_item_has_changed(
$secondOrderItem->getWrappedObject()
]));
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $customer)->willReturn(true);
+
$reorder->getItems()->willReturn(new ArrayCollection([
$firstOrderItem->getWrappedObject(),
$secondOrderItem->getWrappedObject()
@@ -213,14 +234,16 @@ function it_checks_if_price_of_any_item_has_changed(
$entityManager->persist($reorder)->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
- $this->reorder($order, $channel);
+ $this->reorder($order, $channel, $customer);
}
function it_checks_if_any_item_is_out_of_stock(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
ReorderEligibilityChecker $reorderEligibilityChecker,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker,
ChannelInterface $channel,
+ CustomerInterface $customer,
OrderInterface $order,
OrderInterface $reorder,
OrderItemInterface $firstOrderItem,
@@ -244,6 +267,8 @@ function it_checks_if_any_item_is_out_of_stock(
$secondOrderItem->getWrappedObject()
]));
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $customer)->willReturn(true);
+
$reorder->getItems()->willReturn(new ArrayCollection([
$firstOrderItem->getWrappedObject()
]));
@@ -261,6 +286,26 @@ function it_checks_if_any_item_is_out_of_stock(
$entityManager->persist($reorder)->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
- $this->reorder($order, $channel);
+ $this->reorder($order, $channel, $customer);
+ }
+
+ function it_does_not_create_reorder_when_order_does_not_belong_to_given_customer(
+ OrderInterface $order,
+ ChannelInterface $channel,
+ CustomerInterface $firstCustomer,
+ CustomerInterface $secondCustomer,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker
+ ): void {
+ $firstCustomer->getId()->willReturn('1');
+ $secondCustomer->getId()->willReturn('2');
+
+ $order->getCustomer()->willReturn($firstCustomer);
+
+ $orderCustomerRelationChecker->wasOrderPlacedByCustomer($order, $secondCustomer)->shouldBeCalled();
+
+ $this
+ ->shouldThrow(InvalidStateException::class)
+ ->during('reorder', [$order, $channel, $secondCustomer])
+ ;
}
}
diff --git a/src/Checker/OrderCustomerRelationChecker.php b/src/Checker/OrderCustomerRelationChecker.php
new file mode 100644
index 0000000..f8f9048
--- /dev/null
+++ b/src/Checker/OrderCustomerRelationChecker.php
@@ -0,0 +1,22 @@
+getCustomer();
+
+ return
+ null !== $orderCustomer &&
+ $orderCustomer->getId() === $customer->getId()
+ ;
+ }
+}
diff --git a/src/Checker/OrderCustomerRelationCheckerInterface.php b/src/Checker/OrderCustomerRelationCheckerInterface.php
new file mode 100644
index 0000000..d3d997e
--- /dev/null
+++ b/src/Checker/OrderCustomerRelationCheckerInterface.php
@@ -0,0 +1,13 @@
+cartSessionStorage = $cartSessionStorage;
$this->channelContext = $channelContext;
$this->cartContext = $cartContext;
+ $this->customerContext = $customerContext;
$this->orderRepository = $orderRepository;
$this->reorderer = $reorderService;
$this->urlGenerator = $urlGenerator;
@@ -67,10 +74,13 @@ public function __invoke(Request $request): Response
$channel = $this->channelContext->getChannel();
assert($channel instanceof ChannelInterface);
+ /** @var CustomerInterface $customer */
+ $customer = $this->customerContext->getCustomer();
+
$reorder = null;
try {
- $reorder = $this->reorderer->reorder($order, $channel);
+ $reorder = $this->reorderer->reorder($order, $channel, $customer);
} catch (InvalidStateException $exception) {
$this->session->getFlashBag()->add('info', $exception->getMessage());
diff --git a/src/Reorder/Reorderer.php b/src/Reorder/Reorderer.php
index cd0bfab..21439bb 100644
--- a/src/Reorder/Reorderer.php
+++ b/src/Reorder/Reorderer.php
@@ -8,8 +8,10 @@
use Nette\InvalidStateException;
use Sylius\Bundle\MoneyBundle\Formatter\MoneyFormatterInterface;
use Sylius\Component\Core\Model\ChannelInterface;
+use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Order\Processor\OrderProcessorInterface;
+use Sylius\CustomerReorderPlugin\Checker\OrderCustomerRelationCheckerInterface;
use Sylius\CustomerReorderPlugin\Factory\OrderFactoryInterface;
use Sylius\CustomerReorderPlugin\ReorderEligibility\ReorderEligibilityChecker;
use Sylius\CustomerReorderPlugin\ReorderEligibility\ResponseProcessing\ReorderEligibilityCheckerResponseProcessorInterface;
@@ -38,6 +40,9 @@ final class Reorderer implements ReordererInterface
/** @var ReorderEligibilityCheckerResponseProcessorInterface */
private $reorderEligibilityCheckerResponseProcessor;
+ /** @var OrderCustomerRelationCheckerInterface */
+ private $orderCustomerRelationCheckerInterface;
+
public function __construct(
OrderFactoryInterface $orderFactory,
EntityManagerInterface $entityManager,
@@ -45,7 +50,8 @@ public function __construct(
MoneyFormatterInterface $moneyFormatter,
Session $session,
ReorderEligibilityChecker $reorderEligibilityChecker,
- ReorderEligibilityCheckerResponseProcessorInterface $reorderEligibilityCheckerResponseProcessor
+ ReorderEligibilityCheckerResponseProcessorInterface $reorderEligibilityCheckerResponseProcessor,
+ OrderCustomerRelationCheckerInterface $orderCustomerRelationChecker
) {
$this->orderFactory = $orderFactory;
$this->entityManager = $entityManager;
@@ -54,10 +60,18 @@ public function __construct(
$this->session = $session;
$this->reorderEligibilityChecker = $reorderEligibilityChecker;
$this->reorderEligibilityCheckerResponseProcessor = $reorderEligibilityCheckerResponseProcessor;
+ $this->orderCustomerRelationCheckerInterface = $orderCustomerRelationChecker;
}
- public function reorder(OrderInterface $order, ChannelInterface $channel): OrderInterface
- {
+ public function reorder(
+ OrderInterface $order,
+ ChannelInterface $channel,
+ CustomerInterface $customer
+ ): OrderInterface {
+ if (!$this->orderCustomerRelationCheckerInterface->wasOrderPlacedByCustomer($order, $customer)) {
+ throw new InvalidStateException("The customer is not the order's owner.");
+ }
+
$reorder = $this->orderFactory->createFromExistingOrder($order, $channel);
assert($reorder instanceof OrderInterface);
diff --git a/src/Reorder/ReordererInterface.php b/src/Reorder/ReordererInterface.php
index 13fe0d0..d78c439 100644
--- a/src/Reorder/ReordererInterface.php
+++ b/src/Reorder/ReordererInterface.php
@@ -5,9 +5,14 @@
namespace Sylius\CustomerReorderPlugin\Reorder;
use Sylius\Component\Core\Model\ChannelInterface;
+use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
interface ReordererInterface
{
- public function reorder(OrderInterface $order, ChannelInterface $channel): OrderInterface;
+ public function reorder(
+ OrderInterface $order,
+ ChannelInterface $channel,
+ CustomerInterface $customer
+ ): OrderInterface;
}
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
index 03b092b..83e2d1c 100644
--- a/src/Resources/config/services.xml
+++ b/src/Resources/config/services.xml
@@ -8,6 +8,7 @@
+
@@ -21,6 +22,7 @@
+
@@ -66,5 +68,6 @@
+
diff --git a/tests/Behat/Context/Reorder/Application/ReorderContext.php b/tests/Behat/Context/Reorder/Application/ReorderContext.php
new file mode 100644
index 0000000..75d9dde
--- /dev/null
+++ b/tests/Behat/Context/Reorder/Application/ReorderContext.php
@@ -0,0 +1,63 @@
+orderRepository = $orderRepository;
+ $this->customerRepository = $customerRepository;
+ $this->reorderer = $reorderer;
+ }
+
+ /**
+ * @When the customer :customerEmail tries to reorder the order :orderNumber
+ */
+ public function theCustomerTriesToReorderTheOrder(string $customerEmail, string $orderNumber): void
+ {
+ /** @var OrderInterface $order */
+ $order = $this->orderRepository->findOneByNumber($orderNumber);
+
+ /** @var CustomerInterface $customer */
+ $customer = $this->customerRepository->findOneBy(['email' => $customerEmail]);
+
+ try {
+ $this->reorderer->reorder($order, $order->getChannel(), $customer);
+ } catch (InvalidStateException $exception) {
+ return;
+ }
+
+ throw new \Exception("Reorder should fail");
+ }
+
+ /**
+ * @Then the order :orderNumber should not be reordered
+ */
+ public function theOrderShouldNotBeReordered(string $orderNumber): void
+ {
+ // skipped intentionally - not relevant as the condition was checked in previous step
+ }
+}
diff --git a/tests/Behat/Context/Reorder/ReorderContext.php b/tests/Behat/Context/Reorder/Ui/ReorderContext.php
similarity index 99%
rename from tests/Behat/Context/Reorder/ReorderContext.php
rename to tests/Behat/Context/Reorder/Ui/ReorderContext.php
index 0fff470..22b2456 100644
--- a/tests/Behat/Context/Reorder/ReorderContext.php
+++ b/tests/Behat/Context/Reorder/Ui/ReorderContext.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Sylius\CustomerReorderPlugin\Behat\Context\Reorder;
+namespace Tests\Sylius\CustomerReorderPlugin\Behat\Context\Reorder\Ui;
use Behat\Behat\Context\Context;
use Behat\Mink\Session;
diff --git a/tests/Behat/Resources/services.xml b/tests/Behat/Resources/services.xml
index 173f55c..a87d110 100644
--- a/tests/Behat/Resources/services.xml
+++ b/tests/Behat/Resources/services.xml
@@ -2,7 +2,7 @@
-
+
@@ -14,6 +14,14 @@
+
+
+
+
+
+
+
+
diff --git a/tests/Behat/Resources/suites.yml b/tests/Behat/Resources/suites.yml
index 845776a..5a078f8 100644
--- a/tests/Behat/Resources/suites.yml
+++ b/tests/Behat/Resources/suites.yml
@@ -1,8 +1,8 @@
default:
suites:
- reorders:
+ reorders_ui:
contexts_services:
- - sylius_customer_reorder.behat.context.setup.reorder
+ - sylius_customer_reorder.behat.context.ui.reorder
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.product
@@ -29,3 +29,34 @@ default:
- sylius.behat.context.ui.shop.checkout.addressing
filters:
tags: "@reordering && @ui"
+ reorders_application:
+ contexts_services:
+ - sylius_customer_reorder.behat.context.application.reorder
+
+ - sylius.behat.context.setup.channel
+ - sylius.behat.context.setup.customer
+ - sylius.behat.context.setup.product
+ - sylius.behat.context.setup.shipping
+ - sylius.behat.context.setup.payment
+ - sylius.behat.context.setup.order
+ - sylius.behat.context.setup.shop_security
+ - sylius.behat.context.setup.promotion
+
+ - sylius.behat.context.transform.address
+ - sylius.behat.context.transform.channel
+ - sylius.behat.context.transform.customer
+ - sylius.behat.context.transform.lexical
+ - sylius.behat.context.transform.payment
+ - sylius.behat.context.transform.product
+ - sylius.behat.context.transform.promotion
+ - sylius.behat.context.transform.shared_storage
+ - sylius.behat.context.transform.shipping_method
+ - sylius.behat.context.transform.user
+
+ - sylius.behat.context.hook.doctrine_orm
+ - sylius.behat.context.ui.shop.checkout.complete
+ - sylius.behat.context.ui.shop.account
+ - sylius.behat.context.ui.shop.cart
+ - sylius.behat.context.ui.shop.checkout.addressing
+ filters:
+ tags: "@reordering && @application"