diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
index 35dd55f051ea1..58f7cbe0ab104 100644
--- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
+++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
@@ -130,6 +130,7 @@ private function updateItemQuantity(Item $item, float $qty)
{
if ($qty > 0) {
$item->clearMessage();
+ $item->setHasError(false);
$item->setQty($qty);
if ($item->getHasError()) {
diff --git a/app/code/Magento/Checkout/Model/Cart.php b/app/code/Magento/Checkout/Model/Cart.php
index 3c1a70ef7a3d6..4b411e61ddaf8 100644
--- a/app/code/Magento/Checkout/Model/Cart.php
+++ b/app/code/Magento/Checkout/Model/Cart.php
@@ -535,6 +535,8 @@ public function updateItems($data)
$qty = isset($itemInfo['qty']) ? (double)$itemInfo['qty'] : false;
if ($qty > 0) {
+ $item->clearMessage();
+ $item->setHasError(false);
$item->setQty($qty);
if ($item->getHasError()) {
@@ -707,6 +709,9 @@ public function getItemsQty()
*/
public function updateItem($itemId, $requestInfo = null, $updatingParams = null)
{
+ $product = null;
+ $productId = null;
+
try {
$item = $this->getQuote()->getItemById($itemId);
if (!$item) {
@@ -757,6 +762,7 @@ public function updateItem($itemId, $requestInfo = null, $updatingParams = null)
* Getter for RequestInfoFilter
*
* @deprecated 100.1.2
+ * @see MAGETWO-60073
* @return \Magento\Checkout\Model\Cart\RequestInfoFilterInterface
*/
private function getRequestInfoFilter()
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml
new file mode 100644
index 0000000000000..4e42963054662
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.00
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
index 36403d5e22595..cc4ccecc74091 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
@@ -19,6 +19,7 @@ define(
return function (serviceUrl, payload, messageContainer) {
var headers = {};
+ var redirectURL = '';
fullScreenLoader.startLoader();
_.each(hooks.requestModifiers, function (modifier) {
@@ -30,6 +31,13 @@ define(
).fail(
function (response) {
errorProcessor.process(response, messageContainer);
+ redirectURL = response.getResponseHeader('errorRedirectAction');
+
+ if (redirectURL) {
+ setTimeout(function () {
+ errorProcessor.redirectTo(redirectURL);
+ }, 3000);
+ }
}
).done(
function (response) {
diff --git a/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php b/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php
new file mode 100644
index 0000000000000..7808f4cb4af6b
--- /dev/null
+++ b/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php
@@ -0,0 +1,63 @@
+braintreeCCVault = $braintreeCCVault;
+ $this->tokensConfigProvider = $tokensConfigProvider ??
+ ObjectManager::getInstance()->get(TokensConfigProvider::class);
+ }
+
+ /**
+ * Retrieve available payment methods
+ *
+ * @param Container $container
+ * @param array $results
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetMethods(Container $container, array $results): array
+ {
+ $methods = [];
+ foreach ($results as $result) {
+ if ($result->getCode() === $this->braintreeCCVault
+ && empty($this->tokensConfigProvider->getTokensComponents($result->getCode()))) {
+
+ continue;
+ }
+ $methods[] = $result;
+ }
+ return $methods;
+ }
+}
diff --git a/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php b/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php
new file mode 100644
index 0000000000000..9a08f47727bb2
--- /dev/null
+++ b/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php
@@ -0,0 +1,142 @@
+tokensConfigProviderMock = $this->getMockBuilder(TokensConfigProvider::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $objectManagerHelper = new ObjectManager($this);
+ $this->containerMock = $objectManagerHelper->getObject(Container::class);
+
+ $this->plugin = $objectManagerHelper->getObject(
+ PaymentMethodProcess::class,
+ [
+ 'braintreeCCVault' => self::PAYMENT_METHOD_BRAINTREE_CC_VAULT,
+ 'tokensConfigProvider' => $this->tokensConfigProviderMock
+ ]
+ );
+ }
+
+ /**
+ * @param array $methods
+ * @param array $expectedResult
+ * @param array $tokenComponents
+ * @dataProvider afterGetMethodsDataProvider
+ */
+ public function testAfterGetMethods(array $methods, array $expectedResult, array $tokenComponents)
+ {
+
+ $this->tokensConfigProviderMock->method('getTokensComponents')
+ ->with(self::PAYMENT_METHOD_BRAINTREE_CC_VAULT)
+ ->willReturn($tokenComponents);
+
+ $result = $this->plugin->afterGetMethods($this->containerMock, $methods);
+ $this->assertEquals($result, $expectedResult);
+ }
+
+ /**
+ * Data provider for AfterGetMethods.
+ *
+ * @return array
+ */
+ public function afterGetMethodsDataProvider(): array
+ {
+ $tokenUiComponentInterface = $this->getMockBuilder(TokenUiComponentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $checkmoPaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+ $brainTreePaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+ $brainTreeCCVaultTPaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+
+ $checkmoPaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_CHECKMO);
+ $brainTreePaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_BRAINTREE);
+ $brainTreeCCVaultTPaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_BRAINTREE_CC_VAULT);
+
+ $paymentMethods = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ $brainTreeCCVaultTPaymentMethod,
+ ];
+ $expectedResult1 = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ $brainTreeCCVaultTPaymentMethod
+ ];
+ $expectedResult2 = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ ];
+
+ return [
+ [$paymentMethods, $expectedResult1, [$tokenUiComponentInterface]],
+ [$paymentMethods, $expectedResult2, []],
+ ];
+ }
+}
diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json
index 8caad77d9b36b..2f09f9b0c237f 100644
--- a/app/code/Magento/Payment/composer.json
+++ b/app/code/Magento/Payment/composer.json
@@ -13,7 +13,8 @@
"magento/module-quote": "*",
"magento/module-sales": "*",
"magento/module-store": "*",
- "magento/module-ui": "*"
+ "magento/module-ui": "*",
+ "magento/module-vault": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml
index b7422bb00d543..a826dedf9f02c 100644
--- a/app/code/Magento/Payment/etc/di.xml
+++ b/app/code/Magento/Payment/etc/di.xml
@@ -81,4 +81,12 @@
Magento\Payment\Model\Method\VirtualLogger
+
+
+
+
+
+ braintree_cc_vault
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml
new file mode 100644
index 0000000000000..3480fdc4dc9e6
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 102
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js
index 15b392afea22d..12b25abe1dec6 100644
--- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js
+++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js
@@ -585,7 +585,7 @@ define([
applyCoupon: function (code) {
this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {
'order[coupon][code]': code,
- reset_shipping: 0
+ reset_shipping: true
});
this.orderItemChanged = false;
jQuery('html, body').animate({
diff --git a/app/code/Magento/Webapi/Controller/Rest.php b/app/code/Magento/Webapi/Controller/Rest.php
index 1da3731eceb37..f4678167851d1 100644
--- a/app/code/Magento/Webapi/Controller/Rest.php
+++ b/app/code/Magento/Webapi/Controller/Rest.php
@@ -7,6 +7,7 @@
namespace Magento\Webapi\Controller;
use Magento\Framework\Exception\AuthorizationException;
+use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Webapi\Authorization;
use Magento\Framework\Webapi\ErrorProcessor;
use Magento\Framework\Webapi\Rest\Request as RestRequest;
@@ -16,9 +17,9 @@
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Webapi\Controller\Rest\ParamsOverrider;
+use Magento\Webapi\Controller\Rest\RequestProcessorPool;
use Magento\Webapi\Controller\Rest\Router;
use Magento\Webapi\Controller\Rest\Router\Route;
-use Magento\Webapi\Controller\Rest\RequestProcessorPool;
/**
* Front controller for WebAPI REST area.
@@ -38,12 +39,14 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
/**
* @var Router
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected $_router;
/**
* @var Route
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected $_route;
@@ -70,12 +73,14 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
/**
* @var Authorization
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected $authorization;
/**
* @var ServiceInputProcessor
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected $serviceInputProcessor;
@@ -102,6 +107,7 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
/**
* @var ParamsOverrider
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected $paramsOverrider;
@@ -118,6 +124,7 @@ class Rest implements \Magento\Framework\App\FrontControllerInterface
/**
* @var StoreManagerInterface
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
private $storeManager;
@@ -193,6 +200,10 @@ public function dispatch(\Magento\Framework\App\RequestInterface $request)
$this->requestValidator->validate($this->_request);
$processor = $this->requestProcessorPool->getProcessor($this->_request);
$processor->process($this->_request);
+ } catch (CouldNotSaveException $e) {
+ $maskedException = $this->_errorProcessor->maskException($e);
+ $this->_response->setException($maskedException);
+ $this->_response->setHeader('errorRedirectAction', '#shipping');
} catch (\Exception $e) {
$maskedException = $this->_errorProcessor->maskException($e);
$this->_response->setException($maskedException);
@@ -264,7 +275,8 @@ protected function validateRequest()
if ($this->storeManager->getStore()->getCode() === Store::ADMIN_CODE
&& strtoupper($this->_request->getMethod()) === RestRequest::HTTP_METHOD_GET
) {
- throw new \Magento\Framework\Webapi\Exception(__('Cannot perform GET operation with store code \'all\''));
+ throw
+ new \Magento\Framework\Webapi\Exception(__('Cannot perform GET operation with store code \'all\''));
}
}
}
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 4014d16dedea1..36990ffd0ae92 100644
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -1945,9 +1945,11 @@
+
+
- 20
+ 999999
@@ -1970,7 +1972,7 @@
- 20
+ 999999
diff --git a/bin/magento b/bin/magento
index 564d95d38c048..0a5f498dd7e85 100755
--- a/bin/magento
+++ b/bin/magento
@@ -23,6 +23,13 @@ try {
$application->run();
} catch (\Throwable $e) {
while ($e) {
+ if ($e->getFile()) {
+ echo sprintf("\nThere is an error in %s", $e->getFile());
+ if ($e->getLine()) {
+ echo sprintf(" at line: %d", $e->getLine());
+ }
+ echo "\n";
+ }
echo $e->getMessage();
echo $e->getTraceAsString();
echo "\n\n";
diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaB.graphqls b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaB.graphqls
index 08241135a8608..147e8613b22f2 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaB.graphqls
+++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaB.graphqls
@@ -1,3 +1,6 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
type Query {
products(
search: String,
@@ -85,9 +88,7 @@ type ProductTierPrices {
website_id: Float
}
-interface ProductInterface
-@typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite")
-@doc(description: "comment for ProductInterface")
+interface ProductInterface @typeResolver(class:"Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "comment for ProductInterface")
{
id: Int @doc(description: "comment for [ProductInterface].")
name: String
@@ -210,8 +211,11 @@ type CustomizableRadioValue {
type VirtualProduct implements ProductInterface, CustomizableProductInterface {
}
-type SimpleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface
-@doc(description: "Comment for empty SimpleProduct type")
+type SimpleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "Comment for empty SimpleProduct type")
+{
+}
+
+type SimpleProduct implements ProductInterface & PhysicalProductInterface & CustomizableProductInterface @doc(description: "Comment for empty SimpleProduct type")
{
}
@@ -315,8 +319,7 @@ input ProductSortInput @doc(description:"Input ProductSortInput") {
gift_message_available: SortEnum
}
-type MediaGalleryEntry
-@doc(description: "Comment for MediaGalleryEntry type")
+type MediaGalleryEntry @doc(description: "Comment for MediaGalleryEntry type")
{
id: Int @doc(description: "id for MediaGalleryEntry")
media_type: String
diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php
index 47993061d923c..f24d7d7d07c5d 100644
--- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php
+++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php
@@ -12,8 +12,8 @@
use Magento\Framework\Config\FileResolverInterface;
use Magento\Framework\Config\ReaderInterface;
use Magento\Framework\GraphQl\Type\TypeManagement;
-use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface as TypeReaderComposite;
use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\InterfaceType;
+use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface as TypeReaderComposite;
/**
* Reads *.graphqls files from modules and combines the results as array to be used with a library to configure objects
@@ -331,7 +331,8 @@ private function convertInterfacesToAnnotations(string $graphQlSchemaContent): s
$spacePatternNotMandatory = '[\s\t\n\r]*';
preg_match_all(
"/{$spacePattern}{$implementsKindsPattern}{$spacePattern}{$typeNamePattern}"
- . "(,{$spacePatternNotMandatory}$typeNamePattern)*/im",
+ . "(,{$spacePatternNotMandatory}|({$spacePatternNotMandatory}&{$spacePatternNotMandatory})?"
+ . "$typeNamePattern)*/im",
$graphQlSchemaContent,
$allMatchesForImplements
);