diff --git a/features/checkout/emptying_the_cart_after_checkout.feature b/features/checkout/emptying_the_cart_after_checkout.feature index 9d1cc768b93..ef6e9b11945 100644 --- a/features/checkout/emptying_the_cart_after_checkout.feature +++ b/features/checkout/emptying_the_cart_after_checkout.feature @@ -11,7 +11,7 @@ Feature: Emptying the cart after checkout And the store allows paying with "Cash on Delivery" And I am a logged in customer - @ui + @ui @api Scenario: Cart is emptied after the checkout Given I have product "Sig Sauer P226" in the cart And I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" diff --git a/features/checkout/preventing_cart_from_being_modified_after_checkout.feature b/features/checkout/preventing_cart_from_being_modified_after_checkout.feature new file mode 100644 index 00000000000..cf8f1b1b0f4 --- /dev/null +++ b/features/checkout/preventing_cart_from_being_modified_after_checkout.feature @@ -0,0 +1,71 @@ +@checkout +Feature: Preventing cart from being modified after checkout + In order to have order immutable after checkout + As a Customer + I don't want to be able to modify placed order + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "Sig Sauer P226" priced at "$499.99" + And the store has a product "AK-47" priced at "$99.99" + And the store ships everywhere for free + And the store ships everywhere with ups + And the store allows paying with "Cash on Delivery" + And the store also allows paying with "Helicopter Money" + And I am a logged in customer + + @api + Scenario: Preventing from changing billing address after checkout + Given I added product "Sig Sauer P226" to the cart + And I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to change the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + Then I should be informed that cart is no longer available + + @api + Scenario: Preventing from changing shipping method after checkout + Given I added product "Sig Sauer P226" to the cart + And I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to change shipping method to "UPS" + Then I should be informed that cart is no longer available + + @api + Scenario: Preventing from changing payment method after checkout + Given I added product "Sig Sauer P226" to the cart + And I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to change payment method to "Helicopter Money" payment + Then I should be informed that cart is no longer available + + @api + Scenario: Preventing from adding product after checkout + Given I added product "Sig Sauer P226" to the cart + And I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to add product "AK-47" to the cart + Then I should be informed that cart is no longer available + + @api + Scenario: Preventing from removing product after checkout + Given I added product "Sig Sauer P226" to the cart + And I added product "AK-47" to the cart + Then I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to remove product "AK-47" from the cart + Then I should be informed that cart is no longer available + + @api + Scenario: Preventing from changing quantity of product after checkout + Given I added product "Sig Sauer P226" to the cart + And I added product "AK-47" to the cart + Then I specified the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" + And I proceeded with "Free" shipping method and "Cash on Delivery" payment + And I confirmed my order + When I try to change quantity to 2 of product "AK-47" from the cart + Then I should be informed that cart is no longer available diff --git a/src/Sylius/Behat/Context/Api/Shop/CheckoutContext.php b/src/Sylius/Behat/Context/Api/Shop/CheckoutContext.php index 9cd8382bf5a..f6acf71b4d9 100644 --- a/src/Sylius/Behat/Context/Api/Shop/CheckoutContext.php +++ b/src/Sylius/Behat/Context/Api/Shop/CheckoutContext.php @@ -17,6 +17,7 @@ use Behat\Behat\Context\Context; use Sylius\Behat\Client\ApiClientInterface; use Sylius\Behat\Client\ResponseCheckerInterface; +use Sylius\Behat\Service\Converter\AdminToShopIriConverterInterface; use Sylius\Behat\Service\SharedStorageInterface; use Sylius\Component\Core\Formatter\StringInflector; use Sylius\Component\Core\Model\AddressInterface; @@ -27,6 +28,7 @@ use Sylius\Component\Core\Model\ShippingMethodInterface; use Sylius\Component\Core\OrderCheckoutStates; use Sylius\Component\Core\Repository\OrderRepositoryInterface; +use Sylius\Component\Product\Resolver\ProductVariantResolverInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\HttpFoundation\Request; @@ -62,6 +64,9 @@ final class CheckoutContext implements Context /** @var RepositoryInterface */ private $paymentMethodRepository; + /** @var ProductVariantResolverInterface */ + private $productVariantResolver; + /** @var SharedStorageInterface */ private $sharedStorage; @@ -76,6 +81,7 @@ public function __construct( RepositoryInterface $shippingMethodRepository, OrderRepositoryInterface $orderRepository, RepositoryInterface $paymentMethodRepository, + ProductVariantResolverInterface $productVariantResolver, SharedStorageInterface $sharedStorage ) { $this->client = $client; @@ -85,6 +91,7 @@ public function __construct( $this->shippingMethodRepository = $shippingMethodRepository; $this->orderRepository = $orderRepository; $this->paymentMethodRepository = $paymentMethodRepository; + $this->productVariantResolver = $productVariantResolver; $this->sharedStorage = $sharedStorage; } @@ -165,6 +172,7 @@ public function iDoNotSpecifyAnyBillingAddressInformation(AddressInterface $addr /** * @When /^I specified the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)" for "([^"]+)")$/ * @When /^I define the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)" for "([^"]+)")$/ + * @When /^I try to change the billing (address as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)" for "([^"]+)")$/ */ public function iSpecifiedTheBillingAddressAs(AddressInterface $address): void { @@ -217,6 +225,7 @@ public function iProvideAdditionalNotesLike(string $notes): void } /** + * @Given I confirmed my order * @When I confirm my order * @When I try to confirm my order * @When /^the (?:visitor|customer) confirm his order$/ @@ -254,6 +263,8 @@ public function iConfirmMyOrder(): void * @When I select :shippingMethod shipping method * @When /^the (?:visitor|customer) proceed with ("[^"]+" shipping method)$/ * @Given /^the (?:visitor|customer) has proceeded ("[^"]+" shipping method)$/ + * @When /^the visitor try to proceed with ("[^"]+" shipping method) in the customer cart$/ + * @When I try to change shipping method to :shippingMethod */ public function iProceededWithShippingMethod(ShippingMethodInterface $shippingMethod): void { @@ -301,6 +312,7 @@ public function iCompleteTheShippingStepWithFirstShippingMethod(): void * @When I have proceeded selecting :paymentMethod payment method * @When /^the (?:customer|visitor) proceed with ("[^"]+" payment)$/ * @Given /^the (?:customer|visitor) has proceeded ("[^"]+" payment)$/ + * @When I try to change payment method to :paymentMethod payment */ public function iChoosePaymentMethod(PaymentMethodInterface $paymentMethod): void { @@ -688,6 +700,43 @@ public function iShouldBeInformedThatThisPaymentMethodHasBeenDisabled(PaymentMet )); } + /** + * @When /^I try to add (product "[^"]+") to the (cart)$/ + */ + public function iTryToAddProductToCart(ProductInterface $product, string $tokenValue): void + { + $this->putProductToCart($product, $tokenValue); + } + + /** + * @When /^I try to remove (product "[^"]+") from the (cart)$/ + */ + public function iTryToRemoveProductFromTheCart(ProductInterface $product, string $tokenValue): void + { + $this->removeOrderItemFromCart($product->getId(), $tokenValue); + } + + /** + * @When /^I try to change quantity to (\d+) of (product "[^"]+") from the (cart)$/ + */ + public function iTryToChangeQuantityToOfProductFromTheCart(int $quantity, ProductInterface $product, string $tokenValue): void + { + $this->putProductToCart($product, $tokenValue, $quantity); + } + + /** + * @Then I should be informed that cart is no longer available + */ + public function iShouldBeInformedThatCartIsNoLongerAvailable(): void + { + /** @var Response $response */ + $response = $this->client->getResponse(); + + Assert::same($response->getStatusCode(), 404); + + Assert::same($this->responseChecker->getResponseContent($response)['message'], 'Not Found'); + } + private function isViolationWithMessageInResponse(Response $response, string $message): bool { $violations = $this->responseChecker->getResponseContent($response)['violations']; @@ -872,4 +921,40 @@ private function hasProvinceNameInAddress(string $provinceName, string $addressT $provinceName ); } + + private function putProductToCart(ProductInterface $product, string $tokenValue, int $quantity = 1): void + { + $this->client->request( + Request::METHOD_PATCH, + \sprintf( + '/new-api/shop/orders/%s/items', + $tokenValue, + ), + [], + [], + $this->getHeaders(), + json_encode([ + 'productCode' => $product->getCode(), + 'productVariantCode' => $this->productVariantResolver->getVariant($product)->getCode(), + 'quantity' => $quantity, + ], \JSON_THROW_ON_ERROR) + ); + } + + private function removeOrderItemFromCart(int $orderItemId, string $tokenValue): void + { + $this->client->request( + Request::METHOD_PATCH, + \sprintf( + '/new-api/shop/orders/%s/remove', + $tokenValue, + ), + [], + [], + $this->getHeaders(), + json_encode([ + 'orderItemId' => $orderItemId, + ], \JSON_THROW_ON_ERROR) + ); + } } diff --git a/src/Sylius/Behat/Context/Setup/ShippingContext.php b/src/Sylius/Behat/Context/Setup/ShippingContext.php index c1aa3575fb1..9c89ec035b2 100644 --- a/src/Sylius/Behat/Context/Setup/ShippingContext.php +++ b/src/Sylius/Behat/Context/Setup/ShippingContext.php @@ -49,9 +49,6 @@ final class ShippingContext implements Context /** @var ShippingMethodExampleFactory */ private $shippingMethodExampleFactory; - /** @var FactoryInterface */ - private $shippingMethodTranslationFactory; - /** @var FactoryInterface */ private $shippingMethodRuleFactory; @@ -63,7 +60,6 @@ public function __construct( ShippingMethodRepositoryInterface $shippingMethodRepository, RepositoryInterface $zoneRepository, ShippingMethodExampleFactory $shippingMethodExampleFactory, - FactoryInterface $shippingMethodTranslationFactory, FactoryInterface $shippingMethodRuleFactory, ObjectManager $shippingMethodManager ) { @@ -71,7 +67,6 @@ public function __construct( $this->shippingMethodRepository = $shippingMethodRepository; $this->zoneRepository = $zoneRepository; $this->shippingMethodExampleFactory = $shippingMethodExampleFactory; - $this->shippingMethodTranslationFactory = $shippingMethodTranslationFactory; $this->shippingMethodRuleFactory = $shippingMethodRuleFactory; $this->shippingMethodManager = $shippingMethodManager; } @@ -93,15 +88,16 @@ public function storeShipsEverythingForFree(ZoneInterface $zone): void } /** - * @Given the store ships everywhere for free + * @Given the store ships everywhere for :shippingMethodName + * @Given the store ships everywhere with :shippingMethodName */ - public function theStoreShipsEverywhereForFree(): void + public function theStoreShipsEverywhereWith(string $shippingMethodName): void { /** @var ZoneInterface $zone */ foreach ($this->zoneRepository->findBy(['scope' => [CoreScope::SHIPPING, Scope::ALL]]) as $zone) { $this->saveShippingMethod($this->shippingMethodExampleFactory->create([ - 'name' => 'Free', - 'code' => 'FREE-' . $zone->getCode(), + 'name' => ucfirst($shippingMethodName), + 'code' => strtoupper($shippingMethodName) . '-' . $zone->getCode(), 'enabled' => true, 'zone' => $zone, 'calculator' => [ diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml index e5f98f19b76..dda32d0601f 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/api/shop.xml @@ -40,6 +40,7 @@ + diff --git a/src/Sylius/Behat/Resources/config/services/contexts/setup.xml b/src/Sylius/Behat/Resources/config/services/contexts/setup.xml index 6203c394611..40c4f50e132 100644 --- a/src/Sylius/Behat/Resources/config/services/contexts/setup.xml +++ b/src/Sylius/Behat/Resources/config/services/contexts/setup.xml @@ -232,7 +232,6 @@ - diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Cart/AddItemToCartHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Cart/AddItemToCartHandler.php index 9df43bd1104..f77aac20e1e 100644 --- a/src/Sylius/Bundle/ApiBundle/CommandHandler/Cart/AddItemToCartHandler.php +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Cart/AddItemToCartHandler.php @@ -16,6 +16,8 @@ use Sylius\Bundle\ApiBundle\Command\Cart\AddItemToCart; use Sylius\Component\Core\Factory\CartItemFactoryInterface; use Sylius\Component\Core\Model\OrderInterface; +use Sylius\Component\Core\Model\OrderItemInterface; +use Sylius\Component\Core\Model\ProductVariantInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface; use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface; @@ -57,6 +59,7 @@ public function __construct( public function __invoke(AddItemToCart $addItemToCart): OrderInterface { + /** @var ProductVariantInterface|null $productVariant */ $productVariant = $this->productVariantRepository->findOneByCodeAndProductCode( $addItemToCart->productVariantCode, $addItemToCart->productCode @@ -64,10 +67,12 @@ public function __invoke(AddItemToCart $addItemToCart): OrderInterface Assert::notNull($productVariant); + /** @var OrderInterface $cart */ $cart = $this->orderRepository->findCartByTokenValue($addItemToCart->orderTokenValue); Assert::notNull($cart); + /** @var OrderItemInterface $cartItem */ $cartItem = $this->cartItemFactory->createNew(); $cartItem->setVariant($productVariant); diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/OrderItem.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/OrderItem.xml index 99ee52e1761..b08fec4e5e4 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/OrderItem.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/OrderItem.xml @@ -21,7 +21,7 @@ - +