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 @@
-
+