diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php
index 0f10fd633cb5b..5476fd05a0fed 100644
--- a/app/code/Magento/Authorizenet/Model/Directpost.php
+++ b/app/code/Magento/Authorizenet/Model/Directpost.php
@@ -5,10 +5,9 @@
*/
namespace Magento\Authorizenet\Model;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\App\ObjectManager;
use Magento\Payment\Model\Method\ConfigInterface;
use Magento\Payment\Model\Method\TransparentInterface;
-use Magento\Sales\Model\Order\Email\Sender\OrderSender;
/**
* Authorize.net DirectPost payment method model.
@@ -102,7 +101,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra
protected $response;
/**
- * @var OrderSender
+ * @var \Magento\Sales\Model\Order\Email\Sender\OrderSender
*/
protected $orderSender;
@@ -123,6 +122,16 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra
*/
private $psrLogger;
+ /**
+ * @var \Magento\Sales\Api\PaymentFailuresInterface
+ */
+ private $paymentFailures;
+
+ /**
+ * @var \Magento\Sales\Model\Order
+ */
+ private $order;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -134,18 +143,19 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra
* @param \Magento\Framework\Module\ModuleListInterface $moduleList
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Authorizenet\Helper\Data $dataHelper
- * @param Directpost\Request\Factory $requestFactory
- * @param Directpost\Response\Factory $responseFactory
+ * @param \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory
+ * @param \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory
* @param \Magento\Authorizenet\Model\TransactionService $transactionService
* @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
* @param \Magento\Sales\Model\OrderFactory $orderFactory
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
- * @param OrderSender $orderSender
+ * @param \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender
* @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -161,8 +171,8 @@ public function __construct(
\Magento\Authorizenet\Helper\Data $dataHelper,
\Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory,
\Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory,
- TransactionService $transactionService,
- ZendClientFactory $httpClientFactory,
+ \Magento\Authorizenet\Model\TransactionService $transactionService,
+ \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory,
\Magento\Sales\Model\OrderFactory $orderFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
@@ -170,7 +180,8 @@ public function __construct(
\Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null
) {
$this->orderFactory = $orderFactory;
$this->storeManager = $storeManager;
@@ -179,6 +190,8 @@ public function __construct(
$this->orderSender = $orderSender;
$this->transactionRepository = $transactionRepository;
$this->_code = static::METHOD_CODE;
+ $this->paymentFailures = $paymentFailures ? : ObjectManager::getInstance()
+ ->get(\Magento\Sales\Api\PaymentFailuresInterface::class);
parent::__construct(
$context,
@@ -561,13 +574,10 @@ public function process(array $responseData)
$this->validateResponse();
$response = $this->getResponse();
- //operate with order
- $orderIncrementId = $response->getXInvoiceNum();
$responseText = $this->dataHelper->wrapGatewayError($response->getXResponseReasonText());
$isError = false;
- if ($orderIncrementId) {
- /* @var $order \Magento\Sales\Model\Order */
- $order = $this->orderFactory->create()->loadByIncrementId($orderIncrementId);
+ if ($this->getOrderIncrementId()) {
+ $order = $this->getOrderFromResponse();
//check payment method
$payment = $order->getPayment();
if (!$payment || $payment->getMethod() != $this->getCode()) {
@@ -632,9 +642,10 @@ public function checkResponseCode()
return true;
case self::RESPONSE_CODE_DECLINED:
case self::RESPONSE_CODE_ERROR:
- throw new \Magento\Framework\Exception\LocalizedException(
- $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText())
- );
+ $errorMessage = $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText());
+ $order = $this->getOrderFromResponse();
+ $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMessage);
+ throw new \Magento\Framework\Exception\LocalizedException($errorMessage);
default:
throw new \Magento\Framework\Exception\LocalizedException(
__('There was a payment authorization error.')
@@ -988,12 +999,40 @@ protected function getTransactionResponse($transactionId)
private function getPsrLogger()
{
if (null === $this->psrLogger) {
- $this->psrLogger = \Magento\Framework\App\ObjectManager::getInstance()
+ $this->psrLogger = ObjectManager::getInstance()
->get(\Psr\Log\LoggerInterface::class);
}
return $this->psrLogger;
}
+ /**
+ * Fetch order by increment id from response.
+ *
+ * @return \Magento\Sales\Model\Order
+ */
+ private function getOrderFromResponse(): \Magento\Sales\Model\Order
+ {
+ if (!$this->order) {
+ $this->order = $this->orderFactory->create();
+
+ if ($incrementId = $this->getOrderIncrementId()) {
+ $this->order = $this->order->loadByIncrementId($incrementId);
+ }
+ }
+
+ return $this->order;
+ }
+
+ /**
+ * Fetch order increment id from response.
+ *
+ * @return string
+ */
+ private function getOrderIncrementId(): string
+ {
+ return $this->getResponse()->getXInvoiceNum();
+ }
+
/**
* Checks if filter action is Report Only. Transactions that trigger this filter are processed as normal,
* but are also reported in the Merchant Interface as triggering this filter.
diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php
index dbb6ac8333c14..95c67f67852da 100644
--- a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php
+++ b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Authorizenet\Test\Unit\Model;
+use Magento\Sales\Api\PaymentFailuresInterface;
use Magento\Framework\Simplexml\Element;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\Authorizenet\Model\Directpost;
@@ -74,6 +75,14 @@ class DirectpostTest extends \PHPUnit\Framework\TestCase
*/
protected $requestFactory;
+ /**
+ * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $paymentFailures;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
@@ -104,6 +113,12 @@ protected function setUp()
->setMethods(['getTransactionDetails'])
->getMock();
+ $this->paymentFailures = $this->getMockBuilder(
+ PaymentFailuresInterface::class
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
+
$this->requestFactory = $this->getRequestFactoryMock();
$httpClientFactoryMock = $this->getHttpClientFactoryMock();
@@ -117,7 +132,8 @@ protected function setUp()
'responseFactory' => $this->responseFactoryMock,
'transactionRepository' => $this->transactionRepositoryMock,
'transactionService' => $this->transactionServiceMock,
- 'httpClientFactory' => $httpClientFactoryMock
+ 'httpClientFactory' => $httpClientFactoryMock,
+ 'paymentFailures' => $this->paymentFailures,
]
);
}
@@ -313,12 +329,16 @@ public function checkResponseCodeSuccessDataProvider()
}
/**
- * @param bool $responseCode
+ * Checks response failures behaviour.
+ *
+ * @param int $responseCode
+ * @param int $failuresHandlerCalls
+ * @return void
*
* @expectedException \Magento\Framework\Exception\LocalizedException
* @dataProvider checkResponseCodeFailureDataProvider
*/
- public function testCheckResponseCodeFailure($responseCode)
+ public function testCheckResponseCodeFailure(int $responseCode, int $failuresHandlerCalls): void
{
$reasonText = 'reason text';
@@ -333,18 +353,35 @@ public function testCheckResponseCodeFailure($responseCode)
->with($reasonText)
->willReturn(__('Gateway error: %1', $reasonText));
+ $orderMock = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderMock->expects($this->exactly($failuresHandlerCalls))
+ ->method('getQuoteId')
+ ->willReturn(1);
+
+ $this->paymentFailures->expects($this->exactly($failuresHandlerCalls))
+ ->method('handle')
+ ->with(1);
+
+ $reflection = new \ReflectionClass($this->directpost);
+ $order = $reflection->getProperty('order');
+ $order->setAccessible(true);
+ $order->setValue($this->directpost, $orderMock);
+
$this->directpost->checkResponseCode();
}
/**
* @return array
*/
- public function checkResponseCodeFailureDataProvider()
+ public function checkResponseCodeFailureDataProvider(): array
{
return [
- ['responseCode' => Directpost::RESPONSE_CODE_DECLINED],
- ['responseCode' => Directpost::RESPONSE_CODE_ERROR],
- ['responseCode' => 999999]
+ ['responseCode' => Directpost::RESPONSE_CODE_DECLINED, 1],
+ ['responseCode' => Directpost::RESPONSE_CODE_ERROR, 1],
+ ['responseCode' => 999999, 0],
];
}
diff --git a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php
index 1d5057d83d6cf..f9fae8a469b1d 100644
--- a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php
+++ b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php
@@ -24,7 +24,7 @@ class AvsEmsCodeMapper implements PaymentVerificationInterface
*
* @var string
*/
- private static $unavailableCode = 'U';
+ private static $unavailableCode = '';
/**
* List of mapping AVS codes
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php
index 9b80a2237a8fb..c82634d36db31 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php
@@ -84,11 +84,11 @@ public function testGetCodeWithException()
public function getCodeDataProvider()
{
return [
- ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'],
- ['avsZip' => null, 'avsStreet' => 'M', 'expected' => 'U'],
- ['avsZip' => 'M', 'avsStreet' => null, 'expected' => 'U'],
- ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => 'U'],
- ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => 'U'],
+ ['avsZip' => null, 'avsStreet' => null, 'expected' => ''],
+ ['avsZip' => null, 'avsStreet' => 'M', 'expected' => ''],
+ ['avsZip' => 'M', 'avsStreet' => null, 'expected' => ''],
+ ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => ''],
+ ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => ''],
['avsZip' => 'M', 'avsStreet' => 'M', 'expected' => 'Y'],
['avsZip' => 'N', 'avsStreet' => 'M', 'expected' => 'A'],
['avsZip' => 'M', 'avsStreet' => 'N', 'expected' => 'Z'],
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index beb6f2b13bcfe..95339870b4d61 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -3,14 +3,18 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Controller\Adminhtml\Product\Initialization;
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory as CustomOptionFactory;
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory as ProductLinkFactory;
+use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface\Proxy as ProductRepository;
+use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeDefaultValueFilter;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks;
use Magento\Catalog\Model\Product\Link\Resolver as LinkResolver;
+use Magento\Catalog\Model\Product\LinkTypeProvider;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter;
@@ -81,7 +85,7 @@ class Helper
private $dateTimeFilter;
/**
- * @var \Magento\Catalog\Model\Product\LinkTypeProvider
+ * @var LinkTypeProvider
*/
private $linkTypeProvider;
@@ -99,10 +103,10 @@ class Helper
* @param ProductLinks $productLinks
* @param \Magento\Backend\Helper\Js $jsHelper
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
- * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory|null $customOptionFactory
- * @param \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory|null $productLinkFactory
- * @param \Magento\Catalog\Api\ProductRepositoryInterface|null $productRepository
- * @param \Magento\Catalog\Model\Product\LinkTypeProvider|null $linkTypeProvider
+ * @param CustomOptionFactory|null $customOptionFactory
+ * @param ProductLinkFactory |null $productLinkFactory
+ * @param ProductRepositoryInterface|null $productRepository
+ * @param LinkTypeProvider|null $linkTypeProvider
* @param AttributeFilter|null $attributeFilter
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -113,10 +117,10 @@ public function __construct(
\Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks $productLinks,
\Magento\Backend\Helper\Js $jsHelper,
\Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
- \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory = null,
- \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory = null,
- \Magento\Catalog\Api\ProductRepositoryInterface $productRepository = null,
- \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider = null,
+ CustomOptionFactory $customOptionFactory = null,
+ ProductLinkFactory $productLinkFactory = null,
+ ProductRepositoryInterface $productRepository = null,
+ LinkTypeProvider $linkTypeProvider = null,
AttributeFilter $attributeFilter = null
) {
$this->request = $request;
@@ -125,16 +129,13 @@ public function __construct(
$this->productLinks = $productLinks;
$this->jsHelper = $jsHelper;
$this->dateFilter = $dateFilter;
- $this->customOptionFactory = $customOptionFactory ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class);
- $this->productLinkFactory = $productLinkFactory ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Api\Data\ProductLinkInterfaceFactory::class);
- $this->productRepository = $productRepository ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
- $this->linkTypeProvider = $linkTypeProvider ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Model\Product\LinkTypeProvider::class);
- $this->attributeFilter = $attributeFilter ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(AttributeFilter::class);
+
+ $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
+ $this->customOptionFactory = $customOptionFactory ?: $objectManager->get(CustomOptionFactory::class);
+ $this->productLinkFactory = $productLinkFactory ?: $objectManager->get(ProductLinkFactory::class);
+ $this->productRepository = $productRepository ?: $objectManager->get(ProductRepositoryInterface::class);
+ $this->linkTypeProvider = $linkTypeProvider ?: $objectManager->get(LinkTypeProvider::class);
+ $this->attributeFilter = $attributeFilter ?: $objectManager->get(AttributeFilter::class);
}
/**
@@ -150,8 +151,7 @@ public function __construct(
*/
public function initializeFromData(\Magento\Catalog\Model\Product $product, array $productData)
{
- unset($productData['custom_attributes']);
- unset($productData['extension_attributes']);
+ unset($productData['custom_attributes'], $productData['extension_attributes']);
if ($productData) {
$stockData = isset($productData['stock_data']) ? $productData['stock_data'] : [];
@@ -199,28 +199,13 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra
$productData['tier_price'] = isset($productData['tier_price']) ? $productData['tier_price'] : [];
$useDefaults = (array)$this->request->getPost('use_default', []);
-
$productData = $this->attributeFilter->prepareProductAttributes($product, $productData, $useDefaults);
-
$product->addData($productData);
if ($wasLockedMedia) {
$product->lockAttribute('media');
}
- /**
- * Check "Use Default Value" checkboxes values
- */
- foreach ($useDefaults as $attributeCode => $useDefaultState) {
- if ($useDefaultState) {
- $product->setData($attributeCode, null);
- // UI component sends value even if field is disabled, so 'Use Config Settings' must be reset to false
- if ($product->hasData('use_config_' . $attributeCode)) {
- $product->setData('use_config_' . $attributeCode, false);
- }
- }
- }
-
$product = $this->setProductLinks($product);
$product = $this->fillProductOptions($product, $productOptions);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
index ee3a6d491e92f..188b0b22f33bf 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
@@ -28,11 +28,17 @@ class AttributeFilter
* @param array $useDefaults
* @return array
*/
- public function prepareProductAttributes(Product $product, array $productData, array $useDefaults)
+ public function prepareProductAttributes(Product $product, array $productData, array $useDefaults): array
{
- foreach ($productData as $attribute => $value) {
- if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) {
- unset($productData[$attribute]);
+ $attributeList = $product->getAttributes();
+ foreach ($productData as $attributeCode => $attributeValue) {
+ if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attributeCode, $attributeValue)) {
+ unset($productData[$attributeCode]);
+ }
+
+ if (isset($useDefaults[$attributeCode]) && $useDefaults[$attributeCode] === '1') {
+ $productData = $this->prepareDefaultData($attributeList, $attributeCode, $productData);
+ $productData = $this->prepareConfigData($product, $attributeCode, $productData);
}
}
@@ -41,14 +47,54 @@ public function prepareProductAttributes(Product $product, array $productData, a
/**
* @param Product $product
- * @param $useDefaults
- * @param $attribute
- * @param $value
+ * @param string $attributeCode
+ * @param array $productData
+ * @return array
+ */
+ private function prepareConfigData(Product $product, string $attributeCode, array $productData): array
+ {
+ // UI component sends value even if field is disabled, so 'Use Config Settings' must be reset to false
+ if ($product->hasData('use_config_' . $attributeCode)) {
+ $productData['use_config_' . $attributeCode] = false;
+ }
+
+ return $productData;
+ }
+
+ /**
+ * @param array $attributeList
+ * @param string $attributeCode
+ * @param array $productData
+ * @return array
+ */
+ private function prepareDefaultData(array $attributeList, string $attributeCode, array $productData): array
+ {
+ if (isset($attributeList[$attributeCode])) {
+ /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
+ $attribute = $attributeList[$attributeCode];
+ $attributeType = $attribute->getBackendType();
+ // For non-numberic types set the attributeValue to 'false' to trigger their removal from the db
+ if ($attributeType === 'varchar' || $attributeType === 'text' || $attributeType === 'datetime') {
+ $attribute->setIsRequired(false);
+ $productData[$attributeCode] = false;
+ } else {
+ $productData[$attributeCode] = null;
+ }
+ }
+
+ return $productData;
+ }
+
+ /**
+ * @param Product $product
+ * @param array $useDefaults
+ * @param string $attribute
+ * @param mixed $value
* @return bool
*/
- private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) : bool
+ private function isAttributeShouldNotBeUpdated(Product $product, array $useDefaults, $attribute, $value): bool
{
- $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1";
+ $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === '1';
return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute));
}
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index 6a82658342824..03ddab3d44547 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -233,7 +233,8 @@ public function __construct(
public function get($sku, $editMode = false, $storeId = null, $forceReload = false)
{
$cacheKey = $this->getCacheKey([$editMode, $storeId]);
- if (!isset($this->instances[$sku][$cacheKey]) || $forceReload) {
+ $cachedProduct = $this->getProductFromLocalCache($sku, $cacheKey);
+ if ($cachedProduct === null || $forceReload) {
$product = $this->productFactory->create();
$productId = $this->resourceModel->getIdBySku($sku);
@@ -250,11 +251,10 @@ public function get($sku, $editMode = false, $storeId = null, $forceReload = fal
}
$product->load($productId);
$this->cacheProduct($cacheKey, $product);
+ $cachedProduct = $product;
}
- if (!isset($this->instances[$sku])) {
- $sku = trim($sku);
- }
- return $this->instances[$sku][$cacheKey];
+
+ return $cachedProduct;
}
/**
@@ -312,7 +312,7 @@ protected function getCacheKey($data)
private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterface $product)
{
$this->instancesById[$product->getId()][$cacheKey] = $product;
- $this->instances[$product->getSku()][$cacheKey] = $product;
+ $this->saveProductInLocalCache($product, $cacheKey);
if ($this->cacheLimit && count($this->instances) > $this->cacheLimit) {
$offset = round($this->cacheLimit / -2);
@@ -338,7 +338,7 @@ protected function initializeProductData(array $productData, $createNew)
$product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]);
}
} else {
- unset($this->instances[$productData['sku']]);
+ $this->removeProductFromLocalCache($productData['sku']);
$product = $this->get($productData['sku']);
}
@@ -613,7 +613,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
if ($tierPrices !== null) {
$product->setData('tier_price', $tierPrices);
}
- unset($this->instances[$product->getSku()]);
+ $this->removeProductFromLocalCache($product->getSku());
unset($this->instancesById[$product->getId()]);
$this->resourceModel->save($product);
} catch (ConnectionException $exception) {
@@ -650,8 +650,9 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
$e
);
}
- unset($this->instances[$product->getSku()]);
+ $this->removeProductFromLocalCache($product->getSku());
unset($this->instancesById[$product->getId()]);
+
return $this->get($product->getSku(), false, $product->getStoreId());
}
@@ -663,7 +664,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductInterface $product)
$sku = $product->getSku();
$productId = $product->getId();
try {
- unset($this->instances[$product->getSku()]);
+ $this->removeProductFromLocalCache($product->getSku());
unset($this->instancesById[$product->getId()]);
$this->resourceModel->delete($product);
} catch (ValidatorException $e) {
@@ -673,8 +674,9 @@ public function delete(\Magento\Catalog\Api\Data\ProductInterface $product)
__('The "%1" product couldn\'t be removed.', $sku)
);
}
- unset($this->instances[$sku]);
+ $this->removeProductFromLocalCache($sku);
unset($this->instancesById[$productId]);
+
return true;
}
@@ -796,4 +798,54 @@ private function getCollectionProcessor()
}
return $this->collectionProcessor;
}
+
+ /**
+ * Gets product from the local cache by SKU.
+ *
+ * @param string $sku
+ * @param string $cacheKey
+ * @return Product|null
+ */
+ private function getProductFromLocalCache(string $sku, string $cacheKey)
+ {
+ $preparedSku = $this->prepareSku($sku);
+
+ return $this->instances[$preparedSku][$cacheKey] ?? null;
+ }
+
+ /**
+ * Removes product in the local cache.
+ *
+ * @param string $sku
+ * @return void
+ */
+ private function removeProductFromLocalCache(string $sku) :void
+ {
+ $preparedSku = $this->prepareSku($sku);
+ unset($this->instances[$preparedSku]);
+ }
+
+ /**
+ * Saves product in the local cache.
+ *
+ * @param Product $product
+ * @param string $cacheKey
+ * @return void
+ */
+ private function saveProductInLocalCache(Product $product, string $cacheKey) : void
+ {
+ $preparedSku = $this->prepareSku($product->getSku());
+ $this->instances[$preparedSku][$cacheKey] = $product;
+ }
+
+ /**
+ * Converts SKU to lower case and trims.
+ *
+ * @param string $sku
+ * @return string
+ */
+ private function prepareSku(string $sku): string
+ {
+ return mb_strtolower(trim($sku));
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
index 28617addc6d27..424427b871456 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
@@ -3,10 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper;
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
class AttributeFilterTest extends \PHPUnit\Framework\TestCase
{
@@ -16,12 +19,12 @@ class AttributeFilterTest extends \PHPUnit\Framework\TestCase
protected $model;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $objectManagerMock;
/**
- * @var Product|\PHPUnit_Framework_MockObject_MockObject
+ * @var Product|MockObject
*/
protected $productMock;
@@ -44,15 +47,25 @@ public function testPrepareProductAttributes(
$expectedProductData,
$initialProductData
) {
+ /** @var MockObject | Product $productMockMap */
$productMockMap = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
- ->setMethods(['getData'])
+ ->setMethods(['getData', 'getAttributes'])
->getMock();
if (!empty($initialProductData)) {
$productMockMap->expects($this->any())->method('getData')->willReturnMap($initialProductData);
}
+ if ($useDefaults) {
+ $productMockMap
+ ->expects($this->once())
+ ->method('getAttributes')
+ ->willReturn(
+ $this->getProductAttributesMock($useDefaults)
+ );
+ }
+
$actualProductData = $this->model->prepareProductAttributes($productMockMap, $requestProductData, $useDefaults);
$this->assertEquals($expectedProductData, $actualProductData);
}
@@ -69,15 +82,15 @@ public function setupInputDataProvider()
'name' => 'testName',
'sku' => 'testSku',
'price' => '100',
- 'description' => ''
+ 'description' => '',
],
'useDefaults' => [],
'expectedProductData' => [
'name' => 'testName',
'sku' => 'testSku',
- 'price' => '100'
+ 'price' => '100',
],
- 'initialProductData' => []
+ 'initialProductData' => [],
],
'update_product_without_use_defaults' => [
'productData' => [
@@ -85,21 +98,21 @@ public function setupInputDataProvider()
'sku' => 'testSku2',
'price' => '101',
'description' => '',
- 'special_price' => null
+ 'special_price' => null,
],
'useDefaults' => [],
'expectedProductData' => [
'name' => 'testName2',
'sku' => 'testSku2',
'price' => '101',
- 'special_price' => null
+ 'special_price' => null,
],
'initialProductData' => [
['name', 'testName2'],
['sku', 'testSku2'],
['price', '101'],
- ['special_price', null]
- ]
+ ['special_price', null],
+ ],
],
'update_product_without_use_defaults_2' => [
'productData' => [
@@ -107,7 +120,7 @@ public function setupInputDataProvider()
'sku' => 'testSku2',
'price' => '101',
'description' => 'updated description',
- 'special_price' => null
+ 'special_price' => null,
],
'useDefaults' => [],
'expectedProductData' => [
@@ -115,14 +128,14 @@ public function setupInputDataProvider()
'sku' => 'testSku2',
'price' => '101',
'description' => 'updated description',
- 'special_price' => null
+ 'special_price' => null,
],
'initialProductData' => [
['name', 'testName2'],
['sku', 'testSku2'],
['price', '101'],
- ['special_price', null]
- ]
+ ['special_price', null],
+ ],
],
'update_product_with_use_defaults' => [
'productData' => [
@@ -130,25 +143,25 @@ public function setupInputDataProvider()
'sku' => 'testSku2',
'price' => '101',
'description' => '',
- 'special_price' => null
+ 'special_price' => null,
],
'useDefaults' => [
- 'description' => '0'
+ 'description' => '0',
],
'expectedProductData' => [
'name' => 'testName2',
'sku' => 'testSku2',
'price' => '101',
'special_price' => null,
- 'description' => ''
+ 'description' => '',
],
'initialProductData' => [
['name', 'testName2'],
['sku', 'testSku2'],
['price', '101'],
['special_price', null],
- ['description', 'descr text']
- ]
+ ['description', 'descr text'],
+ ],
],
'update_product_with_use_defaults_2' => [
'requestProductData' => [
@@ -156,48 +169,73 @@ public function setupInputDataProvider()
'sku' => 'testSku3',
'price' => '103',
'description' => 'descr modified',
- 'special_price' => '100'
+ 'special_price' => '100',
],
'useDefaults' => [
- 'description' => '0'
+ 'description' => '0',
],
'expectedProductData' => [
'name' => 'testName3',
'sku' => 'testSku3',
'price' => '103',
'special_price' => '100',
- 'description' => 'descr modified'
+ 'description' => 'descr modified',
],
'initialProductData' => [
- ['name', null,'testName2'],
+ ['name', null, 'testName2'],
['sku', null, 'testSku2'],
['price', null, '101'],
- ['description', null, 'descr text']
- ]
+ ['description', null, 'descr text'],
+ ],
],
'update_product_with_use_defaults_3' => [
'requestProductData' => [
'name' => 'testName3',
'sku' => 'testSku3',
'price' => '103',
- 'special_price' => '100'
+ 'special_price' => '100',
+ 'description' => 'descr modified',
],
'useDefaults' => [
- 'description' => '1'
+ 'description' => '1',
],
'expectedProductData' => [
'name' => 'testName3',
'sku' => 'testSku3',
'price' => '103',
'special_price' => '100',
+ 'description' => false,
],
'initialProductData' => [
- ['name', null,'testName2'],
+ ['name', null, 'testName2'],
['sku', null, 'testSku2'],
['price', null, '101'],
- ['description', null, 'descr text']
- ]
+ ['description', null, 'descr text'],
+ ],
],
];
}
+
+ /**
+ * @param array $useDefaults
+ * @return array
+ */
+ private function getProductAttributesMock(array $useDefaults): array
+ {
+ $returnArray = [];
+ foreach ($useDefaults as $attributecode => $isDefault) {
+ if ($isDefault === '1') {
+ /** @var Attribute | MockObject $attribute */
+ $attribute = $this->getMockBuilder(Attribute::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $attribute->expects($this->any())
+ ->method('getBackendType')
+ ->willReturn('varchar');
+
+ $returnArray[$attributecode] = $attribute;
+ }
+ }
+ return $returnArray;
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
index a370cbea13c2b..bf5c3d8276295 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
@@ -381,11 +381,11 @@ public function testGetByIdAbsentProduct()
public function testGetByIdProductInEditMode()
{
$productId = 123;
- $this->productFactoryMock->expects($this->once())->method('create')
- ->will($this->returnValue($this->productMock));
- $this->productMock->expects($this->once())->method('setData')->with('_edit_mode', true);
- $this->productMock->expects($this->once())->method('load')->with($productId);
+ $this->productFactoryMock->method('create')->willReturn($this->productMock);
+ $this->productMock->method('setData')->with('_edit_mode', true);
+ $this->productMock->method('load')->with($productId);
$this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->getById($productId, true));
}
@@ -411,6 +411,7 @@ public function testGetByIdForCacheKeyGenerate($identifier, $editMode, $storeId)
}
$this->productMock->expects($this->once())->method('load')->with($identifier);
$this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($identifier);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
//Second invocation should just return from cache
$this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
@@ -433,6 +434,7 @@ public function testGetByIdForcedReload()
$this->serializerMock->expects($this->exactly(3))->method('serialize');
$this->productMock->expects($this->exactly(4))->method('getId')->willReturn($identifier);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
//second invocation should just return from cache
$this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
@@ -532,6 +534,7 @@ public function testGetByIdWithSetStoreId()
$this->productMock->expects($this->once())->method('setData')->with('store_id', $storeId);
$this->productMock->expects($this->once())->method('load')->with($productId);
$this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->getById($productId, false, $storeId));
}
@@ -585,7 +588,8 @@ public function testSaveNew()
->expects($this->once())
->method('toNestedArray')
->will($this->returnValue($this->productData));
- $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->save($this->productMock));
}
@@ -597,7 +601,8 @@ public function testSaveNew()
public function testSaveUnableToSaveException()
{
$this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']);
- $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null));
+ $this->resourceModelMock->expects($this->exactly(1))
+ ->method('getIdBySku')->willReturn(null);
$this->productFactoryMock->expects($this->exactly(2))
->method('create')
->will($this->returnValue($this->productMock));
@@ -610,7 +615,8 @@ public function testSaveUnableToSaveException()
->expects($this->once())
->method('toNestedArray')
->will($this->returnValue($this->productData));
- $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->model->save($this->productMock);
}
@@ -637,6 +643,7 @@ public function testSaveException()
->method('toNestedArray')
->will($this->returnValue($this->productData));
$this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->model->save($this->productMock);
}
@@ -661,6 +668,7 @@ public function testSaveInvalidProductException()
->method('toNestedArray')
->will($this->returnValue($this->productData));
$this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->model->save($this->productMock);
}
@@ -692,6 +700,7 @@ public function testSaveThrowsTemporaryStateExceptionIfDatabaseConnectionErrorOc
$this->productMock->expects($this->once())
->method('getWebsiteIds')
->willReturn([]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->model->save($this->productMock);
}
@@ -734,9 +743,8 @@ public function testGetList()
{
$searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class);
$collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
-
$this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($collectionMock);
-
+ $this->productMock->method('getSku')->willReturn('simple');
$collectionMock->expects($this->once())->method('addAttributeToSelect')->with('*');
$collectionMock->expects($this->exactly(2))->method('joinAttribute')->withConsecutive(
['status', 'catalog_product/status', 'entity_id', null, 'inner'],
@@ -1299,6 +1307,7 @@ public function testSaveWithDifferentWebsites()
]);
$this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([1,2,3]);
$this->productMock->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]);
+ $this->productMock->method('getSku')->willReturn('simple');
$this->assertEquals($this->productMock, $this->model->save($this->productMock));
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
index a29379647b9e1..0426e389d9aeb 100755
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php
@@ -5,11 +5,10 @@
*/
namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier;
-use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav;
use Magento\Eav\Model\Config;
+use Magento\Eav\Model\Entity\Attribute\Source\SourceInterface;
use Magento\Framework\App\RequestInterface;
-use Magento\Framework\EntityManager\EventManager;
use Magento\Framework\Phrase;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Api\Data\StoreInterface;
@@ -257,7 +256,15 @@ protected function setUp()
$this->searchResultsMock = $this->getMockBuilder(SearchResultsInterface::class)
->getMockForAbstractClass();
$this->eavAttributeMock = $this->getMockBuilder(Attribute::class)
- ->setMethods(['load', 'getAttributeGroupCode', 'getApplyTo', 'getFrontendInput', 'getAttributeCode'])
+ ->setMethods([
+ 'load',
+ 'getAttributeGroupCode',
+ 'getApplyTo',
+ 'getFrontendInput',
+ 'getAttributeCode',
+ 'usesSource',
+ 'getSource',
+ ])
->disableOriginalConstructor()
->getMock();
$this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
@@ -451,64 +458,61 @@ public function testModifyData()
}
/**
- * @param int $productId
+ * @param int|null $productId
* @param bool $productRequired
- * @param string $attrValue
- * @param string $note
+ * @param string|null $attrValue
* @param array $expected
+ * @return void
* @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::isProductExists
* @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::setupAttributeMeta
* @dataProvider setupAttributeMetaDataProvider
*/
- public function testSetupAttributeMetaDefaultAttribute($productId, $productRequired, $attrValue, $note, $expected)
- {
- $configPath = 'arguments/data/config';
+ public function testSetupAttributeMetaDefaultAttribute(
+ $productId,
+ bool $productRequired,
+ $attrValue,
+ array $expected
+ ) : void {
+ $configPath = 'arguments/data/config';
$groupCode = 'product-details';
$sortOrder = '0';
+ $attributeOptions = [
+ ['value' => 1, 'label' => 'Int label'],
+ ['value' => 1.5, 'label' => 'Float label'],
+ ['value' => true, 'label' => 'Boolean label'],
+ ['value' => 'string', 'label' => 'String label'],
+ ['value' => ['test1', 'test2'], 'label' => 'Array label'],
+ ];
+ $attributeOptionsExpected = [
+ ['value' => '1', 'label' => 'Int label'],
+ ['value' => '1.5', 'label' => 'Float label'],
+ ['value' => '1', 'label' => 'Boolean label'],
+ ['value' => 'string', 'label' => 'String label'],
+ ['value' => ['test1', 'test2'], 'label' => 'Array label'],
+ ];
- $this->productMock->expects($this->any())
- ->method('getId')
- ->willReturn($productId);
-
- $this->productAttributeMock->expects($this->any())
- ->method('getIsRequired')
- ->willReturn($productRequired);
-
- $this->productAttributeMock->expects($this->any())
- ->method('getDefaultValue')
- ->willReturn('required_value');
-
- $this->productAttributeMock->expects($this->any())
- ->method('getAttributeCode')
- ->willReturn('code');
-
- $this->productAttributeMock->expects($this->any())
- ->method('getValue')
- ->willReturn('value');
-
- $this->productAttributeMock->expects($this->any())
- ->method('getNote')
- ->willReturn($note);
-
- $this->productAttributeMock->expects($this->any())
- ->method('getDefaultFrontendLabel')
- ->willReturn(new Phrase('mylabel'));
+ $this->productMock->method('getId')->willReturn($productId);
+ $this->productAttributeMock->method('getIsRequired')->willReturn($productRequired);
+ $this->productAttributeMock->method('getDefaultValue')->willReturn('required_value');
+ $this->productAttributeMock->method('getAttributeCode')->willReturn('code');
+ $this->productAttributeMock->method('getValue')->willReturn('value');
$attributeMock = $this->getMockBuilder(AttributeInterface::class)
->setMethods(['getValue'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $attributeMock->expects($this->any())
- ->method('getValue')
- ->willReturn($attrValue);
+ $attributeMock->method('getValue')->willReturn($attrValue);
- $this->productMock->expects($this->any())
- ->method('getCustomAttribute')
- ->willReturn($attributeMock);
+ $this->productMock->method('getCustomAttribute')->willReturn($attributeMock);
+ $this->eavAttributeMock->method('usesSource')->willReturn(true);
+
+ $attributeSource = $this->getMockBuilder(SourceInterface::class)->getMockForAbstractClass();
+ $attributeSource->method('getAllOptions')->willReturn($attributeOptions);
- $this->arrayManagerMock->expects($this->any())
- ->method('set')
+ $this->eavAttributeMock->method('getSource')->willReturn($attributeSource);
+
+ $this->arrayManagerMock->method('set')
->with(
$configPath,
[],
@@ -516,16 +520,21 @@ public function testSetupAttributeMetaDefaultAttribute($productId, $productRequi
)
->willReturn($expected);
- $this->arrayManagerMock->expects($this->any())
+ $this->arrayManagerMock->expects($this->once())
->method('merge')
+ ->with(
+ $this->anything(),
+ $this->anything(),
+ $this->callback(
+ function ($value) use ($attributeOptionsExpected) {
+ return $value['options'] === $attributeOptionsExpected;
+ }
+ )
+ )
->willReturn($expected);
- $this->arrayManagerMock->expects($this->any())
- ->method('get')
- ->willReturn([]);
-
- $this->arrayManagerMock->expects($this->any())
- ->method('exists');
+ $this->arrayManagerMock->method('get')->willReturn([]);
+ $this->arrayManagerMock->method('exists')->willReturn(true);
$this->assertEquals(
$expected,
@@ -539,147 +548,82 @@ public function testSetupAttributeMetaDefaultAttribute($productId, $productRequi
public function setupAttributeMetaDataProvider()
{
return [
- 'default_null_prod_not_new_and_required' => $this->defaultNullProdNotNewAndRequired(),
- 'default_null_prod_not_new_and_not_required' => $this->defaultNullProdNotNewAndNotRequired(),
- 'default_null_prod_new_and_not_required' => $this->defaultNullProdNewAndNotRequired(),
- 'default_null_prod_new_and_required' => $this->defaultNullProdNewAndRequired(),
- 'default_null_prod_new_and_required_and_filled_notice' =>
- $this->defaultNullProdNewAndRequiredAndFilledNotice()
- ];
- }
-
- /**
- * @return array
- */
- private function defaultNullProdNotNewAndRequired()
- {
- return [
- 'productId' => 1,
- 'productRequired' => true,
- 'attrValue' => 'val',
- 'note' => null,
- 'expected' => [
- 'dataType' => null,
- 'formElement' => null,
- 'visible' => null,
- 'required' => true,
- 'notice' => null,
- 'default' => null,
- 'label' => new Phrase('mylabel'),
- 'code' => 'code',
- 'source' => 'product-details',
- 'scopeLabel' => '',
- 'globalScope' => false,
- 'sortOrder' => 0
- ],
- ];
- }
-
- /**
- * @return array
- */
- private function defaultNullProdNotNewAndNotRequired()
- {
- return [
- 'productId' => 1,
- 'productRequired' => false,
- 'attrValue' => 'val',
- 'note' => null,
- 'expected' => [
- 'dataType' => null,
- 'formElement' => null,
- 'visible' => null,
- 'required' => false,
- 'notice' => null,
- 'default' => null,
- 'label' => new Phrase('mylabel'),
- 'code' => 'code',
- 'source' => 'product-details',
- 'scopeLabel' => '',
- 'globalScope' => false,
- 'sortOrder' => 0
+ 'default_null_prod_not_new_and_required' => [
+ 'productId' => 1,
+ 'productRequired' => true,
+ 'attrValue' => 'val',
+ 'expected' => [
+ 'dataType' => null,
+ 'formElement' => null,
+ 'visible' => null,
+ 'required' => true,
+ 'notice' => null,
+ 'default' => null,
+ 'label' => new Phrase(null),
+ 'code' => 'code',
+ 'source' => 'product-details',
+ 'scopeLabel' => '',
+ 'globalScope' => false,
+ 'sortOrder' => 0,
+ ],
],
- ];
- }
-
- /**
- * @return array
- */
- private function defaultNullProdNewAndNotRequired()
- {
- return [
- 'productId' => null,
- 'productRequired' => false,
- 'attrValue' => null,
- 'note' => null,
- 'expected' => [
- 'dataType' => null,
- 'formElement' => null,
- 'visible' => null,
- 'required' => false,
- 'notice' => null,
- 'default' => 'required_value',
- 'label' => new Phrase('mylabel'),
- 'code' => 'code',
- 'source' => 'product-details',
- 'scopeLabel' => '',
- 'globalScope' => false,
- 'sortOrder' => 0
+ 'default_null_prod_not_new_and_not_required' => [
+ 'productId' => 1,
+ 'productRequired' => false,
+ 'attrValue' => 'val',
+ 'expected' => [
+ 'dataType' => null,
+ 'formElement' => null,
+ 'visible' => null,
+ 'required' => false,
+ 'notice' => null,
+ 'default' => null,
+ 'label' => new Phrase(null),
+ 'code' => 'code',
+ 'source' => 'product-details',
+ 'scopeLabel' => '',
+ 'globalScope' => false,
+ 'sortOrder' => 0,
+ ],
],
- ];
- }
-
- /**
- * @return array
- */
- private function defaultNullProdNewAndRequired()
- {
- return [
- 'productId' => null,
- 'productRequired' => false,
- 'attrValue' => null,
- 'note' => null,
- 'expected' => [
- 'dataType' => null,
- 'formElement' => null,
- 'visible' => null,
- 'required' => false,
- 'notice' => null,
- 'default' => 'required_value',
- 'label' => new Phrase('mylabel'),
- 'code' => 'code',
- 'source' => 'product-details',
- 'scopeLabel' => '',
- 'globalScope' => false,
- 'sortOrder' => 0
- ],
- ];
- }
-
- /**
- * @return array
- */
- private function defaultNullProdNewAndRequiredAndFilledNotice()
- {
- return [
- 'productId' => null,
- 'productRequired' => false,
- 'attrValue' => null,
- 'note' => 'example notice',
- 'expected' => [
- 'dataType' => null,
- 'formElement' => null,
- 'visible' => null,
- 'required' => false,
- 'notice' => __('example notice'),
- 'default' => 'required_value',
- 'label' => new Phrase('mylabel'),
- 'code' => 'code',
- 'source' => 'product-details',
- 'scopeLabel' => '',
- 'globalScope' => false,
- 'sortOrder' => 0
+ 'default_null_prod_new_and_not_required' => [
+ 'productId' => null,
+ 'productRequired' => false,
+ 'attrValue' => null,
+ 'expected' => [
+ 'dataType' => null,
+ 'formElement' => null,
+ 'visible' => null,
+ 'required' => false,
+ 'notice' => null,
+ 'default' => 'required_value',
+ 'label' => new Phrase(null),
+ 'code' => 'code',
+ 'source' => 'product-details',
+ 'scopeLabel' => '',
+ 'globalScope' => false,
+ 'sortOrder' => 0,
+ ],
],
+ 'default_null_prod_new_and_required' => [
+ 'productId' => null,
+ 'productRequired' => false,
+ 'attrValue' => null,
+ 'expected' => [
+ 'dataType' => null,
+ 'formElement' => null,
+ 'visible' => null,
+ 'required' => false,
+ 'notice' => null,
+ 'default' => 'required_value',
+ 'label' => new Phrase(null),
+ 'code' => 'code',
+ 'source' => 'product-details',
+ 'scopeLabel' => '',
+ 'globalScope' => false,
+ 'sortOrder' => 0,
+ ],
+ ]
];
}
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
index b216ee8c9c547..0e6f17d761bc3 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
@@ -611,8 +611,9 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
// TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done
$attributeModel = $this->getAttributeModel($attribute);
if ($attributeModel->usesSource()) {
+ $options = $attributeModel->getSource()->getAllOptions();
$meta = $this->arrayManager->merge($configPath, $meta, [
- 'options' => $attributeModel->getSource()->getAllOptions(),
+ 'options' => $this->convertOptionsValueToString($options),
]);
}
@@ -683,6 +684,23 @@ private function getAttributeDefaultValue(ProductAttributeInterface $attribute)
return $attribute->getDefaultValue();
}
+ /**
+ * Convert options value to string.
+ *
+ * @param array $options
+ * @return array
+ */
+ private function convertOptionsValueToString(array $options) : array
+ {
+ array_walk($options, function (&$value) {
+ if (isset($value['value']) && is_scalar($value['value'])) {
+ $value['value'] = (string)$value['value'];
+ }
+ });
+
+ return $options;
+ }
+
/**
* @param ProductAttributeInterface $attribute
* @param array $meta
diff --git a/app/code/Magento/Checkout/Helper/Data.php b/app/code/Magento/Checkout/Helper/Data.php
index b3c2e17e5d678..636d4aaca21f0 100644
--- a/app/code/Magento/Checkout/Helper/Data.php
+++ b/app/code/Magento/Checkout/Helper/Data.php
@@ -9,6 +9,7 @@
use Magento\Quote\Model\Quote\Item\AbstractItem;
use Magento\Store\Model\Store;
use Magento\Store\Model\ScopeInterface;
+use Magento\Sales\Api\PaymentFailuresInterface;
/**
* Checkout default helper
@@ -52,6 +53,11 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
*/
protected $priceCurrency;
+ /**
+ * @var PaymentFailuresInterface
+ */
+ private $paymentFailures;
+
/**
* @param \Magento\Framework\App\Helper\Context $context
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -60,6 +66,7 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
* @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder
* @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
* @param PriceCurrencyInterface $priceCurrency
+ * @param PaymentFailuresInterface|null $paymentFailures
* @codeCoverageIgnore
*/
public function __construct(
@@ -69,7 +76,8 @@ public function __construct(
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
\Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
- PriceCurrencyInterface $priceCurrency
+ PriceCurrencyInterface $priceCurrency,
+ PaymentFailuresInterface $paymentFailures = null
) {
$this->_storeManager = $storeManager;
$this->_checkoutSession = $checkoutSession;
@@ -77,6 +85,8 @@ public function __construct(
$this->_transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
$this->priceCurrency = $priceCurrency;
+ $this->paymentFailures = $paymentFailures ? : \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(PaymentFailuresInterface::class);
parent::__construct($context);
}
@@ -202,126 +212,13 @@ public function getBaseSubtotalInclTax($item)
* @param string $message
* @param string $checkoutType
* @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
- public function sendPaymentFailedEmail($checkout, $message, $checkoutType = 'onepage')
- {
- $this->inlineTranslation->suspend();
-
- $template = $this->scopeConfig->getValue(
- 'checkout/payment_failed/template',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- );
-
- $copyTo = $this->_getEmails('checkout/payment_failed/copy_to', $checkout->getStoreId());
- $copyMethod = $this->scopeConfig->getValue(
- 'checkout/payment_failed/copy_method',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- );
- $bcc = [];
- if ($copyTo && $copyMethod == 'bcc') {
- $bcc = $copyTo;
- }
-
- $_receiver = $this->scopeConfig->getValue(
- 'checkout/payment_failed/receiver',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- );
- $sendTo = [
- [
- 'email' => $this->scopeConfig->getValue(
- 'trans_email/ident_' . $_receiver . '/email',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- ),
- 'name' => $this->scopeConfig->getValue(
- 'trans_email/ident_' . $_receiver . '/name',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- ),
- ],
- ];
-
- if ($copyTo && $copyMethod == 'copy') {
- foreach ($copyTo as $email) {
- $sendTo[] = ['email' => $email, 'name' => null];
- }
- }
- $shippingMethod = '';
- if ($shippingInfo = $checkout->getShippingAddress()->getShippingMethod()) {
- $data = explode('_', $shippingInfo);
- $shippingMethod = $data[0];
- }
-
- $paymentMethod = '';
- if ($paymentInfo = $checkout->getPayment()) {
- $paymentMethod = $paymentInfo->getMethod();
- }
-
- $items = '';
- foreach ($checkout->getAllVisibleItems() as $_item) {
- /* @var $_item \Magento\Quote\Model\Quote\Item */
- $items .=
- $_item->getProduct()->getName() . ' x ' . $_item->getQty() . ' ' . $checkout->getStoreCurrencyCode()
- . ' ' . $_item->getProduct()->getFinalPrice(
- $_item->getQty()
- ) . "\n";
- }
- $total = $checkout->getStoreCurrencyCode() . ' ' . $checkout->getGrandTotal();
-
- foreach ($sendTo as $recipient) {
- $transport = $this->_transportBuilder->setTemplateIdentifier(
- $template
- )->setTemplateOptions(
- [
- 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
- 'store' => Store::DEFAULT_STORE_ID
- ]
- )->setTemplateVars(
- [
- 'reason' => $message,
- 'checkoutType' => $checkoutType,
- 'dateAndTime' => $this->_localeDate->formatDateTime(
- new \DateTime(),
- \IntlDateFormatter::MEDIUM,
- \IntlDateFormatter::MEDIUM
- ),
- 'customer' => $checkout->getCustomerFirstname() . ' ' . $checkout->getCustomerLastname(),
- 'customerEmail' => $checkout->getCustomerEmail(),
- 'billingAddress' => $checkout->getBillingAddress(),
- 'shippingAddress' => $checkout->getShippingAddress(),
- 'shippingMethod' => $this->scopeConfig->getValue(
- 'carriers/' . $shippingMethod . '/title',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- ),
- 'paymentMethod' => $this->scopeConfig->getValue(
- 'payment/' . $paymentMethod . '/title',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- ),
- 'items' => nl2br($items),
- 'total' => $total,
- ]
- )->setFrom(
- $this->scopeConfig->getValue(
- 'checkout/payment_failed/identity',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $checkout->getStoreId()
- )
- )->addTo(
- $recipient['email'],
- $recipient['name']
- )->addBcc(
- $bcc
- )->getTransport();
-
- $transport->sendMessage();
- }
-
- $this->inlineTranslation->resume();
+ public function sendPaymentFailedEmail(
+ \Magento\Quote\Model\Quote $checkout,
+ string $message,
+ string $checkoutType = 'onepage'
+ ): \Magento\Checkout\Helper\Data {
+ $this->paymentFailures->handle((int)$checkout->getId(), $message, $checkoutType);
return $this;
}
diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
index a17cf41585649..333226b7d216f 100644
--- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Checkout\Model;
@@ -10,6 +11,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Framework\Exception\CouldNotSaveException;
+use Magento\Quote\Model\Quote;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -135,13 +137,19 @@ public function savePaymentInformation(
\Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
) {
+ $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
+ /** @var Quote $quote */
+ $quote = $this->cartRepository->getActive($quoteIdMask->getQuoteId());
+
if ($billingAddress) {
$billingAddress->setEmail($email);
- $this->billingAddressManagement->assign($cartId, $billingAddress);
+ $quote->removeAddress($quote->getBillingAddress()->getId());
+ $quote->setBillingAddress($billingAddress);
+ $quote->setDataChanges(true);
} else {
- $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
- $this->cartRepository->getActive($quoteIdMask->getQuoteId())->getBillingAddress()->setEmail($email);
+ $quote->getBillingAddress()->setEmail($email);
}
+ $this->limitShippingCarrier($quote);
$this->paymentMethodManagement->set($cartId, $paymentMethod);
return true;
@@ -169,4 +177,22 @@ private function getLogger()
}
return $this->logger;
}
+
+ /**
+ * Limits shipping rates request by carrier from shipping address.
+ *
+ * @param Quote $quote
+ *
+ * @return void
+ * @see \Magento\Shipping\Model\Shipping::collectRates
+ */
+ private function limitShippingCarrier(Quote $quote) : void
+ {
+ $shippingAddress = $quote->getShippingAddress();
+ if ($shippingAddress && $shippingAddress->getShippingMethod()) {
+ $shippingDataArray = explode('_', $shippingAddress->getShippingMethod());
+ $shippingCarrier = array_shift($shippingDataArray);
+ $shippingAddress->setLimitCarrier($shippingCarrier);
+ }
+ }
}
diff --git a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php
index c403156dc13e9..53132ffaa748b 100644
--- a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php
@@ -6,8 +6,7 @@
namespace Magento\Checkout\Test\Unit\Helper;
-use \Magento\Checkout\Helper\Data;
-
+use Magento\Checkout\Helper\Data;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Store\Model\ScopeInterface;
@@ -24,38 +23,36 @@ class DataTest extends \PHPUnit\Framework\TestCase
/**
* @var Data
*/
- private $_helper;
+ private $helper;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
- private $_transportBuilder;
+ private $transportBuilder;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
- private $_translator;
+ private $translator;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
- protected $_checkoutSession;
+ private $checkoutSession;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
- protected $_scopeConfig;
+ private $scopeConfig;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
- protected $_collectionFactory;
+ private $eventManager;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @inheritdoc
*/
- protected $_eventManager;
-
protected function setUp()
{
$objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -63,191 +60,88 @@ protected function setUp()
$arguments = $objectManagerHelper->getConstructArguments($className);
/** @var \Magento\Framework\App\Helper\Context $context */
$context = $arguments['context'];
- $this->_translator = $arguments['inlineTranslation'];
- $this->_eventManager = $context->getEventManager();
- $this->_scopeConfig = $context->getScopeConfig();
- $this->_scopeConfig->expects($this->any())
+ $this->translator = $arguments['inlineTranslation'];
+ $this->eventManager = $context->getEventManager();
+ $this->scopeConfig = $context->getScopeConfig();
+ $this->scopeConfig->expects($this->any())
->method('getValue')
- ->will(
- $this->returnValueMap(
+ ->willReturnMap(
+ [
+ [
+ 'checkout/payment_failed/template',
+ ScopeInterface::SCOPE_STORE,
+ 8,
+ 'fixture_email_template_payment_failed',
+ ],
+ [
+ 'checkout/payment_failed/receiver',
+ ScopeInterface::SCOPE_STORE,
+ 8,
+ 'sysadmin',
+ ],
+ [
+ 'trans_email/ident_sysadmin/email',
+ ScopeInterface::SCOPE_STORE,
+ 8,
+ 'sysadmin@example.com',
+ ],
+ [
+ 'trans_email/ident_sysadmin/name',
+ ScopeInterface::SCOPE_STORE,
+ 8,
+ 'System Administrator',
+ ],
+ [
+ 'checkout/payment_failed/identity',
+ ScopeInterface::SCOPE_STORE,
+ 8,
+ 'noreply@example.com',
+ ],
+ [
+ 'carriers/ground/title',
+ ScopeInterface::SCOPE_STORE,
+ null,
+ 'Ground Shipping',
+ ],
[
- [
- 'checkout/payment_failed/template',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- 8,
- 'fixture_email_template_payment_failed'
- ],
- [
- 'checkout/payment_failed/receiver',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- 8,
- 'sysadmin'
- ],
- [
- 'trans_email/ident_sysadmin/email',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- 8,
- 'sysadmin@example.com'
- ],
- [
- 'trans_email/ident_sysadmin/name',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- 8,
- 'System Administrator'
- ],
- [
- 'checkout/payment_failed/identity',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- 8,
- 'noreply@example.com'
- ],
- [
- 'carriers/ground/title',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- null,
- 'Ground Shipping'
- ],
- [
- 'payment/fixture-payment-method/title',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- null,
- 'Check Money Order'
- ],
- [
- 'checkout/options/onepage_checkout_enabled',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- null,
- 'One Page Checkout'
- ]
- ]
- )
+ 'payment/fixture-payment-method/title',
+ ScopeInterface::SCOPE_STORE,
+ null,
+ 'Check Money Order',
+ ],
+ [
+ 'checkout/options/onepage_checkout_enabled',
+ ScopeInterface::SCOPE_STORE,
+ null,
+ 'One Page Checkout',
+ ],
+ ]
);
- $this->_checkoutSession = $arguments['checkoutSession'];
+ $this->checkoutSession = $arguments['checkoutSession'];
$arguments['localeDate']->expects($this->any())
->method('formatDateTime')
->willReturn('Oct 02, 2013');
- $this->_transportBuilder = $arguments['transportBuilder'];
+ $this->transportBuilder = $arguments['transportBuilder'];
$this->priceCurrency = $arguments['priceCurrency'];
- $this->_helper = $objectManagerHelper->getObject($className, $arguments);
+ $this->helper = $objectManagerHelper->getObject($className, $arguments);
}
/**
* @return void
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function testSendPaymentFailedEmail()
{
- $shippingAddress = new \Magento\Framework\DataObject(['shipping_method' => 'ground_transportation']);
- $billingAddress = new \Magento\Framework\DataObject(['street' => 'Fixture St']);
-
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'setTemplateOptions'
- )->with(
- [
- 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
- 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
- ]
- )->will(
- $this->returnSelf()
- );
-
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'setTemplateIdentifier'
- )->with(
- 'fixture_email_template_payment_failed'
- )->will(
- $this->returnSelf()
- );
+ $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
+ ->setMethods(['getId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $quoteMock->expects($this->any())->method('getId')->willReturn(1);
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'setFrom'
- )->with(
- 'noreply@example.com'
- )->will(
- $this->returnSelf()
- );
-
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'addTo'
- )->with(
- 'sysadmin@example.com',
- 'System Administrator'
- )->will(
- $this->returnSelf()
- );
-
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'setTemplateVars'
- )->with(
- [
- 'reason' => 'test message',
- 'checkoutType' => 'onepage',
- 'dateAndTime' => 'Oct 02, 2013',
- 'customer' => 'John Doe',
- 'customerEmail' => 'john.doe@example.com',
- 'billingAddress' => $billingAddress,
- 'shippingAddress' => $shippingAddress,
- 'shippingMethod' => 'Ground Shipping',
- 'paymentMethod' => 'Check Money Order',
- 'items' => "Product One x 2 USD 10
\nProduct Two x 3 USD 60
\n",
- 'total' => 'USD 70'
- ]
- )->will(
- $this->returnSelf()
- );
-
- $this->_transportBuilder->expects($this->once())->method('addBcc')->will($this->returnSelf());
- $this->_transportBuilder->expects(
- $this->once()
- )->method(
- 'getTransport'
- )->will(
- $this->returnValue($this->createMock(\Magento\Framework\Mail\TransportInterface::class))
- );
-
- $this->_translator->expects($this->at(1))->method('suspend');
- $this->_translator->expects($this->at(1))->method('resume');
-
- $productOne = $this->createMock(\Magento\Catalog\Model\Product::class);
- $productOne->expects($this->once())->method('getName')->will($this->returnValue('Product One'));
- $productOne->expects($this->once())->method('getFinalPrice')->with(2)->will($this->returnValue(10));
-
- $productTwo = $this->createMock(\Magento\Catalog\Model\Product::class);
- $productTwo->expects($this->once())->method('getName')->will($this->returnValue('Product Two'));
- $productTwo->expects($this->once())->method('getFinalPrice')->with(3)->will($this->returnValue(60));
-
- $quote = new \Magento\Framework\DataObject(
- [
- 'store_id' => 8,
- 'store_currency_code' => 'USD',
- 'grand_total' => 70,
- 'customer_firstname' => 'John',
- 'customer_lastname' => 'Doe',
- 'customer_email' => 'john.doe@example.com',
- 'billing_address' => $billingAddress,
- 'shipping_address' => $shippingAddress,
- 'payment' => new \Magento\Framework\DataObject(['method' => 'fixture-payment-method']),
- 'all_visible_items' => [
- new \Magento\Framework\DataObject(['product' => $productOne, 'qty' => 2]),
- new \Magento\Framework\DataObject(['product' => $productTwo, 'qty' => 3])
- ]
- ]
- );
- $this->assertSame($this->_helper, $this->_helper->sendPaymentFailedEmail($quote, 'test message'));
+ $this->assertSame($this->helper, $this->helper->sendPaymentFailedEmail($quoteMock, 'test message'));
}
/**
@@ -255,14 +149,14 @@ public function testSendPaymentFailedEmail()
*/
public function testGetCheckout()
{
- $this->assertEquals($this->_checkoutSession, $this->_helper->getCheckout());
+ $this->assertEquals($this->checkoutSession, $this->helper->getCheckout());
}
public function testGetQuote()
{
$quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->_checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock));
- $this->assertEquals($quoteMock, $this->_helper->getQuote());
+ $this->checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock));
+ $this->assertEquals($quoteMock, $this->helper->getQuote());
}
public function testFormatPrice()
@@ -270,26 +164,26 @@ public function testFormatPrice()
$price = 5.5;
$quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
$storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['formatPrice', '__wakeup']);
- $this->_checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock));
+ $this->checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock));
$quoteMock->expects($this->once())->method('getStore')->will($this->returnValue($storeMock));
$this->priceCurrency->expects($this->once())->method('format')->will($this->returnValue('5.5'));
- $this->assertEquals('5.5', $this->_helper->formatPrice($price));
+ $this->assertEquals('5.5', $this->helper->formatPrice($price));
}
public function testConvertPrice()
{
$price = 5.5;
$this->priceCurrency->expects($this->once())->method('convertAndFormat')->willReturn($price);
- $this->assertEquals(5.5, $this->_helper->convertPrice($price));
+ $this->assertEquals(5.5, $this->helper->convertPrice($price));
}
public function testCanOnepageCheckout()
{
- $this->_scopeConfig->expects($this->once())->method('getValue')->with(
+ $this->scopeConfig->expects($this->once())->method('getValue')->with(
'checkout/options/onepage_checkout_enabled',
'store'
)->will($this->returnValue(true));
- $this->assertTrue($this->_helper->canOnepageCheckout());
+ $this->assertTrue($this->helper->canOnepageCheckout());
}
public function testIsContextCheckout()
@@ -310,18 +204,18 @@ public function testIsContextCheckout()
public function testIsCustomerMustBeLogged()
{
- $this->_scopeConfig->expects($this->once())->method('isSetFlag')->with(
+ $this->scopeConfig->expects($this->once())->method('isSetFlag')->with(
'checkout/options/customer_must_be_logged',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
)->will($this->returnValue(true));
- $this->assertTrue($this->_helper->isCustomerMustBeLogged());
+ $this->assertTrue($this->helper->isCustomerMustBeLogged());
}
public function testGetPriceInclTax()
{
$itemMock = $this->createPartialMock(\Magento\Framework\DataObject::class, ['getPriceInclTax']);
$itemMock->expects($this->exactly(2))->method('getPriceInclTax')->will($this->returnValue(5.5));
- $this->assertEquals(5.5, $this->_helper->getPriceInclTax($itemMock));
+ $this->assertEquals(5.5, $this->helper->getPriceInclTax($itemMock));
}
public function testGetPriceInclTaxWithoutTax()
@@ -362,7 +256,7 @@ public function testGetSubtotalInclTax()
$expected = 5.5;
$itemMock = $this->createPartialMock(\Magento\Framework\DataObject::class, ['getRowTotalInclTax']);
$itemMock->expects($this->exactly(2))->method('getRowTotalInclTax')->will($this->returnValue($rowTotalInclTax));
- $this->assertEquals($expected, $this->_helper->getSubtotalInclTax($itemMock));
+ $this->assertEquals($expected, $this->helper->getSubtotalInclTax($itemMock));
}
public function testGetSubtotalInclTaxNegative()
@@ -380,7 +274,7 @@ public function testGetSubtotalInclTaxNegative()
$itemMock->expects($this->once())
->method('getDiscountTaxCompensation')->will($this->returnValue($discountTaxCompensation));
$itemMock->expects($this->once())->method('getRowTotal')->will($this->returnValue($rowTotal));
- $this->assertEquals($expected, $this->_helper->getSubtotalInclTax($itemMock));
+ $this->assertEquals($expected, $this->helper->getSubtotalInclTax($itemMock));
}
public function testGetBasePriceInclTaxWithoutQty()
@@ -427,7 +321,7 @@ public function testGetBaseSubtotalInclTax()
$itemMock->expects($this->once())->method('getBaseTaxAmount');
$itemMock->expects($this->once())->method('getBaseDiscountTaxCompensation');
$itemMock->expects($this->once())->method('getBaseRowTotal');
- $this->_helper->getBaseSubtotalInclTax($itemMock);
+ $this->helper->getBaseSubtotalInclTax($itemMock);
}
public function testIsAllowedGuestCheckoutWithoutStore()
@@ -435,9 +329,9 @@ public function testIsAllowedGuestCheckoutWithoutStore()
$quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
$store = null;
$quoteMock->expects($this->once())->method('getStoreId')->will($this->returnValue(1));
- $this->_scopeConfig->expects($this->once())
+ $this->scopeConfig->expects($this->once())
->method('isSetFlag')
->will($this->returnValue(true));
- $this->assertTrue($this->_helper->isAllowedGuestCheckout($quoteMock, $store));
+ $this->assertTrue($this->helper->isAllowedGuestCheckout($quoteMock, $store));
}
}
diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
index da0de5d4f0a3d..853ae0157e64a 100644
--- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
@@ -3,11 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Checkout\Test\Unit\Model;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Quote\Model\Quote;
+use Magento\Quote\Model\Quote\Address;
+use Magento\Quote\Model\QuoteIdMask;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -96,6 +100,7 @@ public function testSavePaymentInformationAndPlaceOrder()
$email = 'email@magento.com';
$paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class);
$billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
+ $this->getMockForAssignBillingAddress($cartId, $billingAddressMock);
$billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf();
@@ -119,10 +124,6 @@ public function testSavePaymentInformationAndPlaceOrder()
->willReturn($adapterMockForCheckout);
$adapterMockForCheckout->expects($this->once())->method('beginTransaction');
$adapterMockForCheckout->expects($this->once())->method('commit');
-
- $this->billingAddressManagementMock->expects($this->once())
- ->method('assign')
- ->with($cartId, $billingAddressMock);
$this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock);
$this->cartManagementMock->expects($this->once())->method('placeOrder')->with($cartId)->willReturn($orderId);
@@ -142,6 +143,7 @@ public function testSavePaymentInformationAndPlaceOrderException()
$paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class);
$billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
+ $this->getMockForAssignBillingAddress($cartId, $billingAddressMock);
$billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf();
$adapterMockForSales = $this->getMockBuilder(AdapterInterface::class)
@@ -164,12 +166,9 @@ public function testSavePaymentInformationAndPlaceOrderException()
->willReturn($adapterMockForCheckout);
$adapterMockForCheckout->expects($this->once())->method('beginTransaction');
$adapterMockForCheckout->expects($this->once())->method('rollback');
-
- $this->billingAddressManagementMock->expects($this->once())
- ->method('assign')
- ->with($cartId, $billingAddressMock);
+
$this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock);
- $exception = new \Exception(__('DB exception'));
+ $exception = new \Magento\Framework\Exception\CouldNotSaveException(__('DB exception'));
$this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception);
$this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock);
@@ -185,11 +184,9 @@ public function testSavePaymentInformation()
$email = 'email@magento.com';
$paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class);
$billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
+ $this->getMockForAssignBillingAddress($cartId, $billingAddressMock);
$billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf();
- $this->billingAddressManagementMock->expects($this->once())
- ->method('assign')
- ->with($cartId, $billingAddressMock);
$this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock);
$this->assertTrue($this->model->savePaymentInformation($cartId, $email, $paymentMock, $billingAddressMock));
@@ -201,13 +198,13 @@ public function testSavePaymentInformationWithoutBillingAddress()
$email = 'email@magento.com';
$paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class);
$billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
+ $quoteMock = $this->createMock(Quote::class);
$billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf();
$this->billingAddressManagementMock->expects($this->never())->method('assign');
$this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock);
- $quoteIdMaskMock = $this->createPartialMock(\Magento\Quote\Model\QuoteIdMask::class, ['getQuoteId', 'load']);
+ $quoteIdMaskMock = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']);
$this->quoteIdMaskFactoryMock->expects($this->once())->method('create')->willReturn($quoteIdMaskMock);
$quoteIdMaskMock->expects($this->once())->method('load')->with($cartId, 'masked_id')->willReturnSelf();
$quoteIdMaskMock->expects($this->once())->method('getQuoteId')->willReturn($cartId);
@@ -228,6 +225,15 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException()
$paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class);
$billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
+ $quoteMock = $this->createMock(Quote::class);
+ $quoteMock->method('getBillingAddress')->willReturn($billingAddressMock);
+ $this->cartRepositoryMock->method('getActive')->with($cartId)->willReturn($quoteMock);
+
+ $quoteIdMask = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']);
+ $this->quoteIdMaskFactoryMock->method('create')->willReturn($quoteIdMask);
+ $quoteIdMask->method('load')->with($cartId, 'masked_id')->willReturnSelf();
+ $quoteIdMask->method('getQuoteId')->willReturn($cartId);
+
$billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf();
$adapterMockForSales = $this->getMockBuilder(AdapterInterface::class)
@@ -250,10 +256,7 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException()
->willReturn($adapterMockForCheckout);
$adapterMockForCheckout->expects($this->once())->method('beginTransaction');
$adapterMockForCheckout->expects($this->once())->method('rollback');
-
- $this->billingAddressManagementMock->expects($this->once())
- ->method('assign')
- ->with($cartId, $billingAddressMock);
+
$this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock);
$phrase = new \Magento\Framework\Phrase(__('DB exception'));
$exception = new \Magento\Framework\Exception\LocalizedException($phrase);
@@ -262,4 +265,57 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException()
$this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock);
}
+
+ /**
+ * @param int $cartId
+ * @param \PHPUnit_Framework_MockObject_MockObject $billingAddressMock
+ * @return void
+ */
+ private function getMockForAssignBillingAddress(
+ int $cartId,
+ \PHPUnit_Framework_MockObject_MockObject $billingAddressMock
+ ) : void {
+ $quoteIdMask = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']);
+ $this->quoteIdMaskFactoryMock->method('create')
+ ->willReturn($quoteIdMask);
+ $quoteIdMask->method('load')
+ ->with($cartId, 'masked_id')
+ ->willReturnSelf();
+ $quoteIdMask->method('getQuoteId')
+ ->willReturn($cartId);
+
+ $billingAddressId = 1;
+ $quote = $this->createMock(Quote::class);
+ $quoteBillingAddress = $this->createMock(Address::class);
+ $quoteShippingAddress = $this->createPartialMock(
+ Address::class,
+ ['setLimitCarrier', 'getShippingMethod']
+ );
+ $this->cartRepositoryMock->method('getActive')
+ ->with($cartId)
+ ->willReturn($quote);
+ $quote->expects($this->once())
+ ->method('getBillingAddress')
+ ->willReturn($quoteBillingAddress);
+ $quote->expects($this->once())
+ ->method('getShippingAddress')
+ ->willReturn($quoteShippingAddress);
+ $quoteBillingAddress->expects($this->once())
+ ->method('getId')
+ ->willReturn($billingAddressId);
+ $quote->expects($this->once())
+ ->method('removeAddress')
+ ->with($billingAddressId);
+ $quote->expects($this->once())
+ ->method('setBillingAddress')
+ ->with($billingAddressMock);
+ $quote->expects($this->once())
+ ->method('setDataChanges')
+ ->willReturnSelf();
+ $quoteShippingAddress->method('getShippingMethod')
+ ->willReturn('flatrate_flatrate');
+ $quoteShippingAddress->expects($this->once())
+ ->method('setLimitCarrier')
+ ->with('flatrate');
+ }
}
diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json
index 5f695adc9f4b4..540565345bd9b 100644
--- a/app/code/Magento/Checkout/composer.json
+++ b/app/code/Magento/Checkout/composer.json
@@ -7,7 +7,6 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-backend": "*",
"magento/module-catalog": "*",
"magento/module-catalog-inventory": "*",
"magento/module-config": "*",
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js
index c707792111c82..0f2b0f4e26869 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js
@@ -182,6 +182,15 @@ define([
});
},
+ /**
+ * Sets window location hash.
+ *
+ * @param {String} hash
+ */
+ setHash: function (hash) {
+ window.location.hash = hash;
+ },
+
/**
* Next step.
*/
@@ -199,7 +208,7 @@ define([
if (steps().length > activeIndex + 1) {
code = steps()[activeIndex + 1].code;
steps()[activeIndex + 1].isVisible(true);
- window.location = window.checkoutConfig.checkoutUrl + '#' + code;
+ this.setHash(code);
document.body.scrollTop = document.documentElement.scrollTop = 0;
}
}
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js
index 72cf4e3d479c3..683a18d0e4ead 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js
@@ -25,6 +25,11 @@ define([
initialize: function () {
this._super();
window.addEventListener('hashchange', _.bind(stepNavigator.handleHash, stepNavigator));
+
+ if (!window.location.hash) {
+ stepNavigator.setHash(stepNavigator.steps().sort(stepNavigator.sortItems)[0].code);
+ }
+
stepNavigator.handleHash();
},
diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php
index 7d0b271b9b137..4fc74fa695829 100644
--- a/app/code/Magento/Customer/Model/AccountManagement.php
+++ b/app/code/Magento/Customer/Model/AccountManagement.php
@@ -873,6 +873,8 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU
} catch (MailException $e) {
// If we are not able to send a new account email, this should be ignored
$this->logger->critical($e);
+ } catch (\UnexpectedValueException $e) {
+ $this->logger->error($e);
}
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
index cfd1729e4e06e..9e3a16a307923 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
@@ -1875,4 +1875,105 @@ private function prepareDateTimeFactory()
return $dateTime;
}
+
+ /**
+ * @return void
+ */
+ public function testCreateAccountUnexpectedValueException(): void
+ {
+ $websiteId = 1;
+ $storeId = null;
+ $defaultStoreId = 1;
+ $customerId = 1;
+ $customerEmail = 'email@email.com';
+ $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu';
+ $exception = new \UnexpectedValueException('Template file was not found');
+
+ $datetime = $this->prepareDateTimeFactory();
+
+ $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class);
+ $address->expects($this->once())
+ ->method('setCustomerId')
+ ->with($customerId);
+ $store = $this->createMock(\Magento\Store\Model\Store::class);
+ $store->expects($this->once())
+ ->method('getId')
+ ->willReturn($defaultStoreId);
+ $website = $this->createMock(\Magento\Store\Model\Website::class);
+ $website->expects($this->atLeastOnce())
+ ->method('getStoreIds')
+ ->willReturn([1, 2, 3]);
+ $website->expects($this->once())
+ ->method('getDefaultStore')
+ ->willReturn($store);
+ $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class);
+ $customer->expects($this->atLeastOnce())
+ ->method('getId')
+ ->willReturn($customerId);
+ $customer->expects($this->atLeastOnce())
+ ->method('getEmail')
+ ->willReturn($customerEmail);
+ $customer->expects($this->atLeastOnce())
+ ->method('getWebsiteId')
+ ->willReturn($websiteId);
+ $customer->expects($this->atLeastOnce())
+ ->method('getStoreId')
+ ->willReturn($storeId);
+ $customer->expects($this->once())
+ ->method('setStoreId')
+ ->with($defaultStoreId);
+ $customer->expects($this->once())
+ ->method('getAddresses')
+ ->willReturn([$address]);
+ $customer->expects($this->once())
+ ->method('setAddresses')
+ ->with(null);
+ $this->customerRepository->expects($this->once())
+ ->method('get')
+ ->with($customerEmail)
+ ->willReturn($customer);
+ $this->share->expects($this->once())
+ ->method('isWebsiteScope')
+ ->willReturn(true);
+ $this->storeManager->expects($this->atLeastOnce())
+ ->method('getWebsite')
+ ->with($websiteId)
+ ->willReturn($website);
+ $this->customerRepository->expects($this->atLeastOnce())
+ ->method('save')
+ ->willReturn($customer);
+ $this->addressRepository->expects($this->atLeastOnce())
+ ->method('save')
+ ->with($address);
+ $this->customerRepository->expects($this->once())
+ ->method('getById')
+ ->with($customerId)
+ ->willReturn($customer);
+ $this->random->expects($this->once())
+ ->method('getUniqueHash')
+ ->willReturn($newLinkToken);
+ $customerSecure = $this->createPartialMock(
+ \Magento\Customer\Model\Data\CustomerSecure::class,
+ ['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']
+ );
+ $customerSecure->expects($this->any())
+ ->method('setRpToken')
+ ->with($newLinkToken);
+ $customerSecure->expects($this->any())
+ ->method('setRpTokenCreatedAt')
+ ->with($datetime)
+ ->willReturnSelf();
+ $customerSecure->expects($this->any())
+ ->method('getPasswordHash')
+ ->willReturn(null);
+ $this->customerRegistry->expects($this->atLeastOnce())
+ ->method('retrieveSecureData')
+ ->willReturn($customerSecure);
+ $this->emailNotificationMock->expects($this->once())
+ ->method('newAccount')
+ ->willThrowException($exception);
+ $this->logger->expects($this->once())->method('error')->with($exception);
+
+ $this->accountManagement->createAccount($customer);
+ }
}
diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php
index 4830ecfbb74b3..a6ecdaf24ebbb 100644
--- a/app/code/Magento/Email/Model/AbstractTemplate.php
+++ b/app/code/Magento/Email/Model/AbstractTemplate.php
@@ -531,14 +531,13 @@ protected function cancelDesignConfig()
*
* @param string $templateId
* @return $this
- * @throws \Magento\Framework\Exception\MailException
*/
public function setForcedArea($templateId)
{
- if ($this->area) {
- throw new \LogicException(__('The area is already set.'));
+ if ($this->area === null) {
+ $this->area = $this->emailConfig->getTemplateArea($templateId);
}
- $this->area = $this->emailConfig->getTemplateArea($templateId);
+
return $this;
}
diff --git a/app/code/Magento/Email/Model/Template/Config.php b/app/code/Magento/Email/Model/Template/Config.php
index bdd9054e7969b..9b45f509d97e5 100644
--- a/app/code/Magento/Email/Model/Template/Config.php
+++ b/app/code/Magento/Email/Model/Template/Config.php
@@ -205,8 +205,9 @@ public function getTemplateFilename($templateId, $designParams = [])
$designParams['module'] = $module;
$file = $this->_getInfo($templateId, 'file');
+ $filename = $this->getFilename($file, $designParams, $module);
- return $this->viewFileSystem->getEmailTemplateFileName($file, $designParams, $module);
+ return $filename;
}
/**
@@ -230,4 +231,26 @@ protected function _getInfo($templateId, $fieldName)
}
return $data[$templateId][$fieldName];
}
+
+ /**
+ * Retrieve template file path.
+ *
+ * @param string $file
+ * @param array $designParams
+ * @param string $module
+ *
+ * @return string
+ *
+ * @throws \UnexpectedValueException
+ */
+ private function getFilename(string $file, array $designParams, string $module): string
+ {
+ $filename = $this->viewFileSystem->getEmailTemplateFileName($file, $designParams, $module);
+
+ if ($filename === false) {
+ throw new \UnexpectedValueException("Template file '{$file}' is not found.");
+ }
+
+ return $filename;
+ }
}
diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php
index 46f3fecfb8848..4f545360616c6 100644
--- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php
+++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php
@@ -117,10 +117,11 @@ protected function setUp()
/**
* Return the model under test with additional methods mocked.
*
- * @param $mockedMethods array
+ * @param array $mockedMethods
+ * @param array $data
* @return \Magento\Email\Model\Template|\PHPUnit_Framework_MockObject_MockObject
*/
- protected function getModelMock(array $mockedMethods = [])
+ protected function getModelMock(array $mockedMethods = [], array $data = [])
{
$helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
return $this->getMockForAbstractClass(
@@ -136,7 +137,8 @@ protected function getModelMock(array $mockedMethods = [])
'scopeConfig' => $this->scopeConfig,
'emailConfig' => $this->emailConfig,
'filterFactory' => $this->filterFactory,
- 'templateFactory' => $this->templateFactory
+ 'templateFactory' => $this->templateFactory,
+ 'data' => $data,
]
),
'',
@@ -431,4 +433,33 @@ public function testGetDesignConfig()
$expectedConfig = ['area' => 'test_area', 'store' => 2];
$this->assertEquals($expectedConfig, $model->getDesignConfig()->getData());
}
+
+ /**
+ * @return void
+ */
+ public function testSetForcedAreaWhenAreaIsNotSet(): void
+ {
+ $templateId = 'test_template';
+ $model = $this->getModelMock([], ['area' => null]);
+
+ $this->emailConfig->expects($this->once())
+ ->method('getTemplateArea')
+ ->with($templateId);
+
+ $model->setForcedArea($templateId);
+ }
+
+ /**
+ * @return void
+ */
+ public function testSetForcedAreaWhenAreaIsSet(): void
+ {
+ $templateId = 'test_template';
+ $model = $this->getModelMock([], ['area' => 'frontend']);
+
+ $this->emailConfig->expects($this->never())
+ ->method('getTemplateArea');
+
+ $model->setForcedArea($templateId);
+ }
}
diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php
index 47c3ac1e7e450..b396f2ede8977 100644
--- a/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php
+++ b/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php
@@ -272,6 +272,20 @@ public function testGetTemplateFilenameWithNoParams()
$this->assertEquals('_files/Fixture/ModuleOne/view/frontend/email/one.html', $actualResult);
}
+ /**
+ * @expectedException \UnexpectedValueException
+ * @expectedExceptionMessage Template file 'one.html' is not found
+ * @return void
+ */
+ public function testGetTemplateFilenameWrongFileName(): void
+ {
+ $this->viewFileSystem->expects($this->once())->method('getEmailTemplateFileName')
+ ->with('one.html', $this->designParams, 'Fixture_ModuleOne')
+ ->willReturn(false);
+
+ $this->model->getTemplateFilename('template_one', $this->designParams);
+ }
+
/**
* @param string $getterMethod
* @param $argument
diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
index f0fce97da512a..055af4162d5f3 100644
--- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
+++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
@@ -21,6 +21,11 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress
*/
protected $agreementsValidator;
+ /**
+ * @var \Magento\Sales\Api\PaymentFailuresInterface
+ */
+ private $paymentFailures;
+
/**
* @param \Magento\Framework\App\Action\Context $context
* @param \Magento\Customer\Model\Session $customerSession
@@ -31,6 +36,8 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress
* @param \Magento\Framework\Url\Helper\Data $urlHelper
* @param \Magento\Customer\Model\Url $customerUrl
* @param \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator
+ * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\App\Action\Context $context,
@@ -41,9 +48,9 @@ public function __construct(
\Magento\Framework\Session\Generic $paypalSession,
\Magento\Framework\Url\Helper\Data $urlHelper,
\Magento\Customer\Model\Url $customerUrl,
- \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator
+ \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator,
+ \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null
) {
- $this->agreementsValidator = $agreementValidator;
parent::__construct(
$context,
$customerSession,
@@ -54,6 +61,11 @@ public function __construct(
$urlHelper,
$customerUrl
);
+
+ $this->agreementsValidator = $agreementValidator;
+ $this->paymentFailures = $paymentFailures ? : $this->_objectManager->get(
+ \Magento\Sales\Api\PaymentFailuresInterface::class
+ );
}
/**
@@ -148,6 +160,8 @@ private function processException(\Exception $exception, string $message): void
*/
protected function _processPaypalApiError($exception)
{
+ $this->paymentFailures->handle((int)$this->_getCheckoutSession()->getQuoteId(), $exception->getMessage());
+
switch ($exception->getCode()) {
case ApiProcessableException::API_MAX_PAYMENT_ATTEMPTS_EXCEEDED:
case ApiProcessableException::API_TRANSACTION_EXPIRED:
diff --git a/app/code/Magento/Paypal/Controller/Payflow.php b/app/code/Magento/Paypal/Controller/Payflow.php
index ab21986bde3ba..78c0536e393ac 100644
--- a/app/code/Magento/Paypal/Controller/Payflow.php
+++ b/app/code/Magento/Paypal/Controller/Payflow.php
@@ -41,6 +41,11 @@ abstract class Payflow extends \Magento\Framework\App\Action\Action
*/
protected $_redirectBlockName = 'payflow.link.iframe';
+ /**
+ * @var \Magento\Sales\Api\PaymentFailuresInterface
+ */
+ private $paymentFailures;
+
/**
* @param \Magento\Framework\App\Action\Context $context
* @param \Magento\Checkout\Model\Session $checkoutSession
@@ -48,6 +53,7 @@ abstract class Payflow extends \Magento\Framework\App\Action\Action
* @param \Magento\Paypal\Model\PayflowlinkFactory $payflowModelFactory
* @param \Magento\Paypal\Helper\Checkout $checkoutHelper
* @param \Psr\Log\LoggerInterface $logger
+ * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures
*/
public function __construct(
\Magento\Framework\App\Action\Context $context,
@@ -55,14 +61,19 @@ public function __construct(
\Magento\Sales\Model\OrderFactory $orderFactory,
\Magento\Paypal\Model\PayflowlinkFactory $payflowModelFactory,
\Magento\Paypal\Helper\Checkout $checkoutHelper,
- \Psr\Log\LoggerInterface $logger
+ \Psr\Log\LoggerInterface $logger,
+ \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null
) {
+ parent::__construct($context);
+
$this->_checkoutSession = $checkoutSession;
$this->_orderFactory = $orderFactory;
$this->_logger = $logger;
$this->_payflowModelFactory = $payflowModelFactory;
$this->_checkoutHelper = $checkoutHelper;
- parent::__construct($context);
+ $this->paymentFailures = $paymentFailures ?: $this->_objectManager->get(
+ \Magento\Sales\Api\PaymentFailuresInterface::class
+ );
}
/**
@@ -74,6 +85,10 @@ public function __construct(
protected function _cancelPayment($errorMsg = '')
{
$errorMsg = trim(strip_tags($errorMsg));
+ $order = $this->_checkoutSession->getLastRealOrder();
+ if ($order->getId()) {
+ $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMsg);
+ }
$gotoSection = false;
$this->_checkoutHelper->cancelCurrentOrder($errorMsg);
diff --git a/app/code/Magento/Paypal/Controller/Transparent/Response.php b/app/code/Magento/Paypal/Controller/Transparent/Response.php
index 23ac20ca8c87b..c54dd529588b9 100644
--- a/app/code/Magento/Paypal/Controller/Transparent/Response.php
+++ b/app/code/Magento/Paypal/Controller/Transparent/Response.php
@@ -14,6 +14,8 @@
use Magento\Paypal\Model\Payflow\Service\Response\Transaction;
use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator;
use Magento\Paypal\Model\Payflow\Transparent;
+use Magento\Sales\Api\PaymentFailuresInterface;
+use Magento\Framework\Session\Generic as Session;
/**
* Class Response
@@ -47,6 +49,16 @@ class Response extends \Magento\Framework\App\Action\Action
*/
private $transparent;
+ /**
+ * @var PaymentFailuresInterface
+ */
+ private $paymentFailures;
+
+ /**
+ * @var Session
+ */
+ private $sessionTransparent;
+
/**
* Constructor
*
@@ -56,6 +68,8 @@ class Response extends \Magento\Framework\App\Action\Action
* @param ResponseValidator $responseValidator
* @param LayoutFactory $resultLayoutFactory
* @param Transparent $transparent
+ * @param Session|null $sessionTransparent
+ * @param PaymentFailuresInterface|null $paymentFailures
*/
public function __construct(
Context $context,
@@ -63,7 +77,9 @@ public function __construct(
Transaction $transaction,
ResponseValidator $responseValidator,
LayoutFactory $resultLayoutFactory,
- Transparent $transparent
+ Transparent $transparent,
+ Session $sessionTransparent = null,
+ PaymentFailuresInterface $paymentFailures = null
) {
parent::__construct($context);
$this->coreRegistry = $coreRegistry;
@@ -71,6 +87,8 @@ public function __construct(
$this->responseValidator = $responseValidator;
$this->resultLayoutFactory = $resultLayoutFactory;
$this->transparent = $transparent;
+ $this->sessionTransparent = $sessionTransparent ?: $this->_objectManager->get(Session::class);
+ $this->paymentFailures = $paymentFailures ?: $this->_objectManager->get(PaymentFailuresInterface::class);
}
/**
@@ -86,6 +104,7 @@ public function execute()
} catch (LocalizedException $exception) {
$parameters['error'] = true;
$parameters['error_msg'] = $exception->getMessage();
+ $this->paymentFailures->handle((int)$this->sessionTransparent->getQuoteId(), $parameters['error_msg']);
}
$this->coreRegistry->register(Iframe::REGISTRY_KEY, $parameters);
diff --git a/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php b/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php
index 661d1f3814a0b..1ec7f4832bcb2 100644
--- a/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php
+++ b/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php
@@ -24,7 +24,7 @@ class AvsEmsCodeMapper implements PaymentVerificationInterface
*
* @var string
*/
- private static $unavailableCode = 'U';
+ private static $unavailableCode = '';
/**
* List of mapping AVS codes
diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php
index 9d215ca6cbe17..da5599984b701 100644
--- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php
+++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php
@@ -11,7 +11,6 @@
use Magento\Paypal\Model\Payflow\Transparent;
use Magento\Paypal\Model\Payflowpro;
use Magento\Quote\Model\Quote;
-use Magento\Sales\Model\Order\Payment;
/**
* Class SecureToken
@@ -59,6 +58,7 @@ public function __construct(
*/
public function requestToken(Quote $quote)
{
+ $this->transparent->setStore($quote->getStoreId());
$request = $this->transparent->buildBasicRequest();
$request->setTrxtype(Payflowpro::TRXTYPE_AUTH_ONLY);
diff --git a/app/code/Magento/Paypal/Model/Payflowlink.php b/app/code/Magento/Paypal/Model/Payflowlink.php
index 792309bd76cf9..1955ef3c67661 100644
--- a/app/code/Magento/Paypal/Model/Payflowlink.php
+++ b/app/code/Magento/Paypal/Model/Payflowlink.php
@@ -10,6 +10,7 @@
use Magento\Payment\Model\Method\ConfigInterfaceFactory;
use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface;
use Magento\Sales\Api\Data\OrderPaymentInterface;
+use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Email\Sender\OrderSender;
/**
@@ -239,11 +240,13 @@ public function initialize($paymentAction, $stateObject)
case \Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH:
case \Magento\Paypal\Model\Config::PAYMENT_ACTION_SALE:
$payment = $this->getInfoInstance();
+ /** @var Order $order */
$order = $payment->getOrder();
$order->setCanSendNewEmailFlag(false);
$payment->setAmountAuthorized($order->getTotalDue());
$payment->setBaseAmountAuthorized($order->getBaseTotalDue());
$this->_generateSecureSilentPostHash($payment);
+ $this->setStore($order->getStoreId());
$request = $this->_buildTokenRequest($payment);
$response = $this->postRequest($request, $this->getConfig());
$this->_processTokenErrors($response, $payment);
diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php
index 125aa0f6e65a7..b5fdaf4ae9fd4 100644
--- a/app/code/Magento/Paypal/Model/Payflowpro.php
+++ b/app/code/Magento/Paypal/Model/Payflowpro.php
@@ -647,7 +647,7 @@ public function buildBasicRequest()
*
* @param DataObject $response
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Payment\Gateway\Command\CommandException
* @throws \Magento\Framework\Exception\State\InvalidTransitionException
*/
public function processErrors(DataObject $response)
@@ -659,9 +659,9 @@ public function processErrors(DataObject $response)
} elseif ($response->getResultCode() != self::RESPONSE_CODE_APPROVED &&
$response->getResultCode() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER
) {
- throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg()));
+ throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg()));
} elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_FILTER) {
- throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg()));
+ throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg()));
}
}
diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php
index e25864bbc2f3c..bd4da25cb84d0 100644
--- a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Paypal\Test\Unit\Controller\Payflow;
+use Magento\Sales\Api\PaymentFailuresInterface;
use Magento\Checkout\Block\Onepage\Success;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Action\Context;
@@ -90,6 +91,11 @@ class ReturnUrlTest extends \PHPUnit\Framework\TestCase
*/
private $objectManager;
+ /**
+ * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $paymentFailures;
+
/**
* @inheritdoc
*/
@@ -138,6 +144,17 @@ protected function setUp()
->setMethods(['getLastRealOrderId', 'getLastRealOrder', 'restoreQuote'])
->getMock();
+ $this->quote = $this->getMockBuilder(CartInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->context->expects($this->any())->method('getView')->willReturn($this->view);
+ $this->context->expects($this->any())->method('getRequest')->willReturn($this->request);
+
+ $this->paymentFailures = $this->getMockBuilder(PaymentFailuresInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
$this->context->method('getView')
->willReturn($this->view);
$this->context->method('getRequest')
@@ -148,6 +165,7 @@ protected function setUp()
'checkoutSession' => $this->checkoutSession,
'orderFactory' => $this->orderFactory,
'checkoutHelper' => $this->checkoutHelper,
+ 'paymentFailures' => $this->paymentFailures,
]);
}
@@ -321,6 +339,7 @@ public function testCheckAdvancedAcceptingByPaymentMethod()
'checkoutSession' => $this->checkoutSession,
'orderFactory' => $this->orderFactory,
'checkoutHelper' => $this->checkoutHelper,
+ 'paymentFailures' => $this->paymentFailures,
]);
$returnUrl->execute();
diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php
index a10d103860c65..acefebb779200 100644
--- a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php
@@ -8,16 +8,16 @@
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Registry;
+use Magento\Framework\Session\Generic as Session;
use Magento\Framework\View\Result\Layout;
use Magento\Framework\View\Result\LayoutFactory;
use Magento\Paypal\Controller\Transparent\Response;
use Magento\Paypal\Model\Payflow\Service\Response\Transaction;
use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator;
use Magento\Paypal\Model\Payflow\Transparent;
+use Magento\Sales\Api\PaymentFailuresInterface;
/**
- * Class ResponseTest
- *
* Test for class \Magento\Paypal\Controller\Transparent\Response
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -53,6 +53,19 @@ class ResponseTest extends \PHPUnit\Framework\TestCase
*/
private $payflowFacade;
+ /**
+ * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $paymentFailures;
+
+ /**
+ * @var Session|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $sessionTransparent;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class)
@@ -97,6 +110,14 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods([])
->getMock();
+ $this->paymentFailures = $this->getMockBuilder(PaymentFailuresInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['handle'])
+ ->getMock();
+ $this->sessionTransparent = $this->getMockBuilder(Session::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getQuoteId'])
+ ->getMock();
$this->object = new Response(
$this->contextMock,
@@ -104,7 +125,9 @@ protected function setUp()
$this->transactionMock,
$this->responseValidatorMock,
$this->resultLayoutFactoryMock,
- $this->payflowFacade
+ $this->payflowFacade,
+ $this->sessionTransparent,
+ $this->paymentFailures
);
}
@@ -131,6 +154,8 @@ public function testExecute()
$this->resultLayoutMock->expects($this->once())
->method('getLayout')
->willReturn($this->getLayoutMock());
+ $this->paymentFailures->expects($this->never())
+ ->method('handle');
$this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $this->object->execute());
}
@@ -156,6 +181,12 @@ public function testExecuteWithException()
$this->resultLayoutMock->expects($this->once())
->method('getLayout')
->willReturn($this->getLayoutMock());
+ $this->sessionTransparent->method('getQuoteId')
+ ->willReturn(1);
+ $this->paymentFailures->expects($this->once())
+ ->method('handle')
+ ->with(1)
+ ->willReturnSelf();
$this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $this->object->execute());
}
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php
index eb259043a2d4f..ea86a04206f7b 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php
@@ -85,17 +85,17 @@ public function testGetCodeWithException()
public function getCodeDataProvider()
{
return [
- ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'],
- ['avsZip' => null, 'avsStreet' => 'Y', 'expected' => 'U'],
- ['avsZip' => 'Y', 'avsStreet' => null, 'expected' => 'U'],
+ ['avsZip' => null, 'avsStreet' => null, 'expected' => ''],
+ ['avsZip' => null, 'avsStreet' => 'Y', 'expected' => ''],
+ ['avsZip' => 'Y', 'avsStreet' => null, 'expected' => ''],
['avsZip' => 'Y', 'avsStreet' => 'Y', 'expected' => 'Y'],
['avsZip' => 'N', 'avsStreet' => 'Y', 'expected' => 'A'],
['avsZip' => 'Y', 'avsStreet' => 'N', 'expected' => 'Z'],
['avsZip' => 'N', 'avsStreet' => 'N', 'expected' => 'N'],
- ['avsZip' => 'X', 'avsStreet' => 'Y', 'expected' => 'U'],
- ['avsZip' => 'N', 'avsStreet' => 'X', 'expected' => 'U'],
- ['avsZip' => '', 'avsStreet' => 'Y', 'expected' => 'U'],
- ['avsZip' => 'N', 'avsStreet' => '', 'expected' => 'U']
+ ['avsZip' => 'X', 'avsStreet' => 'Y', 'expected' => ''],
+ ['avsZip' => 'N', 'avsStreet' => 'X', 'expected' => ''],
+ ['avsZip' => '', 'avsStreet' => 'Y', 'expected' => ''],
+ ['avsZip' => 'N', 'avsStreet' => '', 'expected' => '']
];
}
}
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php
index d4a7db25cae89..d8e54ad28fcc8 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php
@@ -10,6 +10,9 @@
use Magento\Framework\UrlInterface;
use Magento\Paypal\Model\Payflow\Service\Request\SecureToken;
use Magento\Paypal\Model\Payflow\Transparent;
+use Magento\Paypal\Model\PayflowConfig;
+use Magento\Quote\Model\Quote;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
* Test class for \Magento\Paypal\Model\Payflow\Service\Request\SecureToken
@@ -19,23 +22,26 @@ class SecureTokenTest extends \PHPUnit\Framework\TestCase
/**
* @var SecureToken
*/
- protected $model;
+ private $model;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject|Transparent
+ * @var Transparent|MockObject
*/
- protected $transparent;
+ private $transparent;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject|Random
+ * @var Random|MockObject
*/
- protected $mathRandom;
+ private $mathRandom;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject|UrlInterface
+ * @var UrlInterface|MockObject
*/
- protected $url;
+ private $url;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->url = $this->createMock(\Magento\Framework\UrlInterface::class);
@@ -52,11 +58,29 @@ protected function setUp()
public function testRequestToken()
{
$request = new DataObject();
+ $storeId = 1;
$secureTokenID = 'Sdj46hDokds09c8k2klaGJdKLl032ekR';
+ $response = new DataObject([
+ 'result' => '0',
+ 'respmsg' => 'Approved',
+ 'securetoken' => '80IgSbabyj0CtBDWHZZeQN3',
+ 'securetokenid' => $secureTokenID,
+ 'result_code' => '0',
+ ]);
+
+ $quote = $this->getMockBuilder(Quote::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $quote->expects($this->once())
+ ->method('getStoreId')
+ ->willReturn($storeId);
$this->transparent->expects($this->once())
->method('buildBasicRequest')
->willReturn($request);
+ $this->transparent->expects($this->once())
+ ->method('setStore')
+ ->with($storeId);
$this->transparent->expects($this->once())
->method('fillCustomerContacts');
$this->transparent->expects($this->once())
@@ -64,7 +88,7 @@ public function testRequestToken()
->willReturn($this->createMock(\Magento\Paypal\Model\PayflowConfig::class));
$this->transparent->expects($this->once())
->method('postRequest')
- ->willReturn(new DataObject());
+ ->willReturn($response);
$this->mathRandom->expects($this->once())
->method('getUniqueHash')
@@ -73,8 +97,6 @@ public function testRequestToken()
$this->url->expects($this->exactly(3))
->method('getUrl');
- $quote = $this->createMock(\Magento\Quote\Model\Quote::class);
-
$this->model->requestToken($quote);
$this->assertEquals($secureTokenID, $request->getSecuretokenid());
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php
index 362615e965d1b..80c8194e07654 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php
@@ -101,16 +101,20 @@ protected function setUp()
public function testInitialize()
{
+ $storeId = 1;
$order = $this->createMock(\Magento\Sales\Model\Order::class);
+ $order->expects($this->exactly(2))
+ ->method('getStoreId')
+ ->willReturn($storeId);
$this->infoInstance->expects($this->any())
->method('getOrder')
- ->will($this->returnValue($order));
+ ->willReturn($order);
$this->infoInstance->expects($this->any())
->method('setAdditionalInformation')
- ->will($this->returnSelf());
+ ->willReturnSelf();
$this->paypalConfig->expects($this->once())
->method('getBuildNotationCode')
- ->will($this->returnValue('build notation code'));
+ ->willReturn('build notation code');
$response = new \Magento\Framework\DataObject(
[
@@ -148,6 +152,7 @@ public function testInitialize()
$stateObject = new \Magento\Framework\DataObject();
$this->model->initialize(\Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH, $stateObject);
+ self::assertEquals($storeId, $this->model->getStore(), '{Store} should be set');
}
/**
diff --git a/app/code/Magento/Quote/Model/PaymentMethodManagement.php b/app/code/Magento/Quote/Model/PaymentMethodManagement.php
index 91d8fe4dbcffd..b6e4bcf5ccc8f 100644
--- a/app/code/Magento/Quote/Model/PaymentMethodManagement.php
+++ b/app/code/Magento/Quote/Model/PaymentMethodManagement.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Quote\Model;
@@ -52,38 +53,37 @@ public function set($cartId, \Magento\Quote\Api\Data\PaymentInterface $method)
{
/** @var \Magento\Quote\Model\Quote $quote */
$quote = $this->quoteRepository->get($cartId);
-
+ $quote->setTotalsCollectedFlag(false);
$method->setChecks([
\Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_CHECKOUT,
\Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_FOR_COUNTRY,
\Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_FOR_CURRENCY,
\Magento\Payment\Model\Method\AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX,
]);
- $payment = $quote->getPayment();
-
- $data = $method->getData();
- $payment->importData($data);
if ($quote->isVirtual()) {
- $quote->getBillingAddress()->setPaymentMethod($payment->getMethod());
+ $address = $quote->getBillingAddress();
} else {
+ $address = $quote->getShippingAddress();
// check if shipping address is set
- if ($quote->getShippingAddress()->getCountryId() === null) {
+ if ($address->getCountryId() === null) {
throw new InvalidTransitionException(
__('The shipping address is missing. Set the address and try again.')
);
}
- $quote->getShippingAddress()->setPaymentMethod($payment->getMethod());
- }
- if (!$quote->isVirtual() && $quote->getShippingAddress()) {
- $quote->getShippingAddress()->setCollectShippingRates(true);
+ $address->setCollectShippingRates(true);
}
+ $paymentData = $method->getData();
+ $payment = $quote->getPayment();
+ $payment->importData($paymentData);
+ $address->setPaymentMethod($payment->getMethod());
+
if (!$this->zeroTotalValidator->isApplicable($payment->getMethodInstance(), $quote)) {
throw new InvalidTransitionException(__('The requested Payment Method is not available.'));
}
- $quote->setTotalsCollectedFlag(false)->collectTotals()->save();
+ $quote->save();
return $quote->getPayment()->getId();
}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php
index 68b077fcdb965..f18d1fa1b06e5 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Quote\Test\Unit\Model;
@@ -152,8 +153,8 @@ public function testSetVirtualProduct()
->with($paymentMethod)
->willReturnSelf();
- $quoteMock->expects($this->exactly(2))->method('getPayment')->willReturn($paymentMock);
- $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(true);
+ $quoteMock->method('getPayment')->willReturn($paymentMock);
+ $quoteMock->expects($this->once())->method('isVirtual')->willReturn(true);
$quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($billingAddressMock);
$methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class);
@@ -165,7 +166,6 @@ public function testSetVirtualProduct()
->willReturn(true);
$quoteMock->expects($this->once())->method('setTotalsCollectedFlag')->with(false)->willReturnSelf();
- $quoteMock->expects($this->once())->method('collectTotals')->willReturnSelf();
$quoteMock->expects($this->once())->method('save')->willReturnSelf();
$paymentMock->expects($this->once())->method('getId')->willReturn($paymentId);
@@ -218,9 +218,9 @@ public function testSetVirtualProductThrowsExceptionIfPaymentMethodNotAvailable(
->with($paymentMethod)
->willReturnSelf();
- $quoteMock->expects($this->once())->method('getPayment')->willReturn($paymentMock);
- $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(true);
- $quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($billingAddressMock);
+ $quoteMock->method('getPayment')->willReturn($paymentMock);
+ $quoteMock->method('isVirtual')->willReturn(true);
+ $quoteMock->method('getBillingAddress')->willReturn($billingAddressMock);
$methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class);
$paymentMock->expects($this->once())->method('getMethodInstance')->willReturn($methodInstance);
@@ -268,17 +268,20 @@ public function testSetSimpleProduct()
$shippingAddressMock = $this->createPartialMock(
\Magento\Quote\Model\Quote\Address::class,
- ['getCountryId', 'setPaymentMethod']
+ ['getCountryId', 'setPaymentMethod', 'setCollectShippingRates']
);
$shippingAddressMock->expects($this->once())->method('getCountryId')->willReturn(100);
$shippingAddressMock->expects($this->once())
->method('setPaymentMethod')
->with($paymentMethod)
->willReturnSelf();
+ $shippingAddressMock->expects($this->once())
+ ->method('setCollectShippingRates')
+ ->with(true);
- $quoteMock->expects($this->exactly(2))->method('getPayment')->willReturn($paymentMock);
- $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(false);
- $quoteMock->expects($this->exactly(4))->method('getShippingAddress')->willReturn($shippingAddressMock);
+ $quoteMock->method('getPayment')->willReturn($paymentMock);
+ $quoteMock->method('isVirtual')->willReturn(false);
+ $quoteMock->method('getShippingAddress')->willReturn($shippingAddressMock);
$methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class);
$paymentMock->expects($this->once())->method('getMethodInstance')->willReturn($methodInstance);
@@ -289,7 +292,6 @@ public function testSetSimpleProduct()
->willReturn(true);
$quoteMock->expects($this->once())->method('setTotalsCollectedFlag')->with(false)->willReturnSelf();
- $quoteMock->expects($this->once())->method('collectTotals')->willReturnSelf();
$quoteMock->expects($this->once())->method('save')->willReturnSelf();
$paymentMock->expects($this->once())->method('getId')->willReturn($paymentId);
@@ -303,7 +305,6 @@ public function testSetSimpleProduct()
public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet()
{
$cartId = 100;
- $methodData = ['method' => 'data'];
$quoteMock = $this->createPartialMock(
\Magento\Quote\Model\Quote::class,
@@ -311,6 +312,7 @@ public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet()
);
$this->quoteRepositoryMock->expects($this->once())->method('get')->with($cartId)->willReturn($quoteMock);
+ /** @var \Magento\Quote\Model\Quote\Payment|\PHPUnit_Framework_MockObject_MockObject $methodMock */
$methodMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Payment::class, ['setChecks', 'getData']);
$methodMock->expects($this->once())
->method('setChecks')
@@ -321,17 +323,13 @@ public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet()
\Magento\Payment\Model\Method\AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX,
])
->willReturnSelf();
- $methodMock->expects($this->once())->method('getData')->willReturn($methodData);
-
- $paymentMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Payment::class, ['importData']);
- $paymentMock->expects($this->once())->method('importData')->with($methodData)->willReturnSelf();
+ $methodMock->expects($this->never())->method('getData');
$shippingAddressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, ['getCountryId']);
$shippingAddressMock->expects($this->once())->method('getCountryId')->willReturn(null);
- $quoteMock->expects($this->once())->method('getPayment')->willReturn($paymentMock);
- $quoteMock->expects($this->once())->method('isVirtual')->willReturn(false);
- $quoteMock->expects($this->once())->method('getShippingAddress')->willReturn($shippingAddressMock);
+ $quoteMock->method('isVirtual')->willReturn(false);
+ $quoteMock->method('getShippingAddress')->willReturn($shippingAddressMock);
$this->model->set($cartId, $methodMock);
}
diff --git a/app/code/Magento/Sales/Api/PaymentFailuresInterface.php b/app/code/Magento/Sales/Api/PaymentFailuresInterface.php
new file mode 100644
index 0000000000000..485ff1ffbe248
--- /dev/null
+++ b/app/code/Magento/Sales/Api/PaymentFailuresInterface.php
@@ -0,0 +1,28 @@
+orderRepository = $orderRepository;
$this->historyRepository = $historyRepository;
@@ -76,6 +84,8 @@ public function __construct(
$this->notifier = $notifier;
$this->eventManager = $eventManager;
$this->orderCommentSender = $orderCommentSender;
+ $this->paymentFailures = $paymentFailures ? : \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Sales\Api\PaymentFailuresInterface::class);
}
/**
@@ -192,6 +202,9 @@ public function place(\Magento\Sales\Api\Data\OrderInterface $order)
return $this->orderRepository->save($order);
//commit
} catch (\Exception $e) {
+ if ($e instanceof CommandException) {
+ $this->paymentFailures->handle((int)$order->getQuoteId(), __($e->getMessage()));
+ }
throw $e;
//rollback;
}
diff --git a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php
new file mode 100644
index 0000000000000..3a49bbce256ef
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php
@@ -0,0 +1,294 @@
+ Configuration > Sales > Checkout > Payment Failed Emails configuration.
+ */
+class PaymentFailuresService implements PaymentFailuresInterface
+{
+ /**
+ * Store config
+ *
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
+ /**
+ * @var StateInterface
+ */
+ private $inlineTranslation;
+
+ /**
+ * @var TransportBuilder
+ */
+ private $transportBuilder;
+
+ /**
+ * @var TimezoneInterface
+ */
+ private $localeDate;
+
+ /**
+ * @var CartRepositoryInterface
+ */
+ private $cartRepository;
+
+ /**
+ * @param ScopeConfigInterface $scopeConfig
+ * @param StateInterface $inlineTranslation
+ * @param TransportBuilder $transportBuilder
+ * @param TimezoneInterface $localeDate
+ * @param CartRepositoryInterface $cartRepository
+ */
+ public function __construct(
+ ScopeConfigInterface $scopeConfig,
+ StateInterface $inlineTranslation,
+ TransportBuilder $transportBuilder,
+ TimezoneInterface $localeDate,
+ CartRepositoryInterface $cartRepository
+ ) {
+ $this->scopeConfig = $scopeConfig;
+ $this->inlineTranslation = $inlineTranslation;
+ $this->transportBuilder = $transportBuilder;
+ $this->localeDate = $localeDate;
+ $this->cartRepository = $cartRepository;
+ }
+
+ /**
+ * Sends an email about failed transaction.
+ *
+ * @param int $cartId
+ * @param string $message
+ * @param string $checkoutType
+ * @return PaymentFailuresInterface
+ */
+ public function handle(
+ int $cartId,
+ string $message,
+ string $checkoutType = 'onepage'
+ ): PaymentFailuresInterface {
+ $this->inlineTranslation->suspend();
+ $quote = $this->cartRepository->get($cartId);
+
+ $template = $this->getConfigValue('checkout/payment_failed/template', $quote);
+ $receiver = $this->getConfigValue('checkout/payment_failed/receiver', $quote);
+ $sendTo = [
+ [
+ 'email' => $this->getConfigValue('trans_email/ident_' . $receiver . '/email', $quote),
+ 'name' => $this->getConfigValue('trans_email/ident_' . $receiver . '/name', $quote),
+ ],
+ ];
+
+ $copyMethod = $this->getConfigValue('checkout/payment_failed/copy_method', $quote);
+ $copyTo = $this->getConfigEmails($quote);
+
+ $bcc = [];
+ if (!empty($copyTo)) {
+ switch ($copyMethod) {
+ case 'bcc':
+ $bcc = $copyTo;
+ break;
+ case 'copy':
+ foreach ($copyTo as $email) {
+ $sendTo[] = ['email' => $email, 'name' => null];
+ }
+ break;
+ }
+ }
+
+ foreach ($sendTo as $recipient) {
+ $transport = $this->transportBuilder
+ ->setTemplateIdentifier($template)
+ ->setTemplateOptions([
+ 'area' => FrontNameResolver::AREA_CODE,
+ 'store' => Store::DEFAULT_STORE_ID,
+ ])
+ ->setTemplateVars($this->getTemplateVars($quote, $message, $checkoutType))
+ ->setFrom($this->getSendFrom($quote))
+ ->addTo($recipient['email'], $recipient['name'])
+ ->addBcc($bcc)
+ ->getTransport();
+
+ $transport->sendMessage();
+ }
+
+ $this->inlineTranslation->resume();
+
+ return $this;
+ }
+
+ /**
+ * Returns mail template variables.
+ *
+ * @param Quote $quote
+ * @param string $message
+ * @param string $checkoutType
+ * @return array
+ */
+ private function getTemplateVars(Quote $quote, string $message, string $checkoutType): array
+ {
+ return [
+ 'reason' => $message,
+ 'checkoutType' => $checkoutType,
+ 'dateAndTime' => $this->getLocaleDate(),
+ 'customer' => $this->getCustomerName($quote),
+ 'customerEmail' => $quote->getBillingAddress()->getEmail(),
+ 'billingAddress' => $quote->getBillingAddress(),
+ 'shippingAddress' => $quote->getShippingAddress(),
+ 'shippingMethod' => $this->getConfigValue(
+ 'carriers/' . $this->getShippingMethod($quote) . '/title',
+ $quote
+ ),
+ 'paymentMethod' => $this->getConfigValue(
+ 'payment/' . $this->getPaymentMethod($quote) . '/title',
+ $quote
+ ),
+ 'items' => implode('
', $this->getQuoteItems($quote)),
+ 'total' => $quote->getCurrency()->getStoreCurrencyCode() . ' ' . $quote->getGrandTotal(),
+ ];
+ }
+
+ /**
+ * Returns scope config value by config path.
+ *
+ * @param string $configPath
+ * @param Quote $quote
+ * @return mixed
+ */
+ private function getConfigValue(string $configPath, Quote $quote)
+ {
+ return $this->scopeConfig->getValue(
+ $configPath,
+ ScopeInterface::SCOPE_STORE,
+ $quote->getStoreId()
+ );
+ }
+
+ /**
+ * Returns shipping method from quote.
+ *
+ * @param Quote $quote
+ * @return string
+ */
+ private function getShippingMethod(Quote $quote): string
+ {
+ $shippingMethod = '';
+ $shippingInfo = $quote->getShippingAddress()->getShippingMethod();
+
+ if ($shippingInfo) {
+ $data = explode('_', $shippingInfo);
+ $shippingMethod = $data[0];
+ }
+
+ return $shippingMethod;
+ }
+
+ /**
+ * Returns payment method title from quote.
+ *
+ * @param Quote $quote
+ * @return string
+ */
+ private function getPaymentMethod(Quote $quote): string
+ {
+ $paymentMethod = $quote->getPayment()->getMethod() ?? '';
+
+ return $paymentMethod;
+ }
+
+ /**
+ * Returns quote visible items.
+ *
+ * @param Quote $quote
+ * @return array
+ */
+ private function getQuoteItems(Quote $quote): array
+ {
+ $items = [];
+ foreach ($quote->getAllVisibleItems() as $item) {
+ $itemData = $item->getProduct()->getName() . ' x ' . $item->getQty() . ' ' .
+ $quote->getCurrency()->getStoreCurrencyCode() . ' ' .
+ $item->getProduct()->getFinalPrice($item->getQty());
+ $items[] = $itemData;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Gets email values by configuration path.
+ *
+ * @param Quote $quote
+ * @return array|false
+ */
+ private function getConfigEmails(Quote $quote)
+ {
+ $configData = $this->getConfigValue('checkout/payment_failed/copy_to', $quote);
+ if (!empty($configData)) {
+ return explode(',', $configData);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns sender identity.
+ *
+ * @param Quote $quote
+ * @return string
+ */
+ private function getSendFrom(Quote $quote): string
+ {
+ return $this->getConfigValue('checkout/payment_failed/identity', $quote);
+ }
+
+ /**
+ * Returns current locale date and time
+ *
+ * @return string
+ */
+ private function getLocaleDate(): string
+ {
+ return $this->localeDate->formatDateTime(
+ new \DateTime(),
+ \IntlDateFormatter::MEDIUM,
+ \IntlDateFormatter::MEDIUM
+ );
+ }
+
+ /**
+ * Returns customer name.
+ *
+ * @param Quote $quote
+ * @return string
+ */
+ private function getCustomerName(Quote $quote): string
+ {
+ $customer = __('Guest')->render();
+ if (!$quote->getCustomerIsGuest()) {
+ $customer = $quote->getCustomer()->getFirstname() . ' ' .
+ $quote->getCustomer()->getLastname();
+ }
+
+ return $customer;
+ }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index ce2948983edbe..ac25cdab22f56 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -65,6 +65,7 @@
+
diff --git a/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php b/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php
index a26beda520944..5be5ccbc5e55a 100644
--- a/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php
+++ b/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php
@@ -60,7 +60,7 @@ public function __construct(
*
* @param string $paymentCode
* @return PaymentVerificationInterface
- * @throws \Exception
+ * @throws ConfigurationMismatchException
*/
public function createPaymentCvv($paymentCode)
{
@@ -73,7 +73,7 @@ public function createPaymentCvv($paymentCode)
*
* @param string $paymentCode
* @return PaymentVerificationInterface
- * @throws \Exception
+ * @throws ConfigurationMismatchException
*/
public function createPaymentAvs($paymentCode)
{
diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php
index 02031e6f5b9b5..19408e99ae02e 100644
--- a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php
+++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php
@@ -30,7 +30,7 @@ class DebuggerFactory
/**
* DebuggerFactory constructor.
*
- * @param bjectManagerInterface $objectManager
+ * @param ObjectManagerInterface $objectManager
* @param Config $config
*/
public function __construct(
diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php
index 858ce0f0f3287..5e544e4b4048e 100644
--- a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php
+++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php
@@ -7,12 +7,13 @@
use Magento\Framework\App\Area;
use Magento\Framework\Config\ScopeInterface;
+use Magento\Framework\Exception\ConfigurationMismatchException;
use Magento\Framework\Intl\DateTimeFactory;
use Magento\Sales\Api\Data\OrderPaymentInterface;
use Magento\Sales\Model\Order;
+use Magento\Signifyd\Model\PaymentMethodMapper\PaymentMethodMapper;
use Magento\Signifyd\Model\PaymentVerificationFactory;
use Magento\Signifyd\Model\SignifydOrderSessionId;
-use Magento\Signifyd\Model\PaymentMethodMapper\PaymentMethodMapper;
/**
* Prepare data related to purchase event represented in case creation request.
@@ -72,6 +73,7 @@ public function __construct(
*
* @param Order $order
* @return array
+ * @throws ConfigurationMismatchException
*/
public function build(Order $order)
{
@@ -202,6 +204,7 @@ private function getOrderChannel()
*
* @param OrderPaymentInterface $orderPayment
* @return string
+ * @throws ConfigurationMismatchException
*/
private function getAvsCode(OrderPaymentInterface $orderPayment)
{
@@ -214,6 +217,7 @@ private function getAvsCode(OrderPaymentInterface $orderPayment)
*
* @param OrderPaymentInterface $orderPayment
* @return string
+ * @throws ConfigurationMismatchException
*/
private function getCvvCode(OrderPaymentInterface $orderPayment)
{
diff --git a/app/code/Magento/Signifyd/etc/di.xml b/app/code/Magento/Signifyd/etc/di.xml
index 92ad8a0bfd87a..fd78fff27f619 100644
--- a/app/code/Magento/Signifyd/etc/di.xml
+++ b/app/code/Magento/Signifyd/etc/di.xml
@@ -15,11 +15,7 @@
-
-
- U
-
-
+
diff --git a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml
index 345f063a7aaa3..f14df1c70a790 100644
--- a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml
+++ b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml
@@ -10,32 +10,18 @@
Swagger UI
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -43,6 +29,7 @@
+
diff --git a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml
index 27b3767f274bc..b20da68734579 100644
--- a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml
+++ b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml
@@ -12,11 +12,48 @@
* Modified by Magento, Modifications Copyright © Magento, Inc. All rights reserved.
*/
-/** @var \Magento\Swagger\Block\Index $block */
+/** @var \Magento\Swagger\Block\Index $block
+ *
+ * @codingStandardsIgnoreFile
+ */
$schemaUrl = $block->getSchemaUrl();
?>
+
+