diff --git a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php
new file mode 100644
index 0000000000000..5832e5bf0a35b
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php
@@ -0,0 +1,71 @@
+ 'Y',
+ 'NM' => 'A',
+ 'MN' => 'Z',
+ 'NN' => 'N',
+ 'UU' => 'U',
+ 'II' => 'U',
+ 'AA' => 'E'
+ ];
+
+ /**
+ * Gets payment AVS verification code.
+ *
+ * @param OrderPaymentInterface $orderPayment
+ * @return string
+ * @throws \InvalidArgumentException If specified order payment has different payment method code.
+ */
+ public function getCode(OrderPaymentInterface $orderPayment)
+ {
+ if ($orderPayment->getMethod() !== ConfigProvider::CODE) {
+ throw new \InvalidArgumentException(
+ 'The "' . $orderPayment->getMethod() . '" does not supported by Braintree AVS mapper.'
+ );
+ }
+
+ $additionalInfo = $orderPayment->getAdditionalInformation();
+ if (empty($additionalInfo[PaymentDetailsHandler::AVS_POSTAL_RESPONSE_CODE]) ||
+ empty($additionalInfo[PaymentDetailsHandler::AVS_STREET_ADDRESS_RESPONSE_CODE])
+ ) {
+ return self::$unavailableCode;
+ }
+
+ $streetCode = $additionalInfo[PaymentDetailsHandler::AVS_STREET_ADDRESS_RESPONSE_CODE];
+ $zipCode = $additionalInfo[PaymentDetailsHandler::AVS_POSTAL_RESPONSE_CODE];
+ $key = $zipCode . $streetCode;
+ return isset(self::$avsMap[$key]) ? self::$avsMap[$key] : self::$unavailableCode;
+ }
+}
diff --git a/app/code/Magento/Braintree/Model/CvvEmsCodeMapper.php b/app/code/Magento/Braintree/Model/CvvEmsCodeMapper.php
new file mode 100644
index 0000000000000..5accc222c1fe5
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/CvvEmsCodeMapper.php
@@ -0,0 +1,66 @@
+ 'M',
+ 'N' => 'N',
+ 'U' => 'P',
+ 'I' => 'P',
+ 'S' => 'S',
+ 'A' => ''
+ ];
+
+ /**
+ * Gets payment CVV verification code.
+ *
+ * @param OrderPaymentInterface $orderPayment
+ * @return string
+ * @throws \InvalidArgumentException If specified order payment has different payment method code.
+ */
+ public function getCode(OrderPaymentInterface $orderPayment)
+ {
+ if ($orderPayment->getMethod() !== ConfigProvider::CODE) {
+ throw new \InvalidArgumentException(
+ 'The "' . $orderPayment->getMethod() . '" does not supported by Braintree CVV mapper.'
+ );
+ }
+
+ $additionalInfo = $orderPayment->getAdditionalInformation();
+ if (empty($additionalInfo[PaymentDetailsHandler::CVV_RESPONSE_CODE])) {
+ return self::$notProvidedCode;
+ }
+
+ $cvv = $additionalInfo[PaymentDetailsHandler::CVV_RESPONSE_CODE];
+ return isset(self::$cvvMap[$cvv]) ? self::$cvvMap[$cvv] : self::$notProvidedCode;
+ }
+}
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php
new file mode 100644
index 0000000000000..586a60fe91f3b
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php
@@ -0,0 +1,101 @@
+mapper = new AvsEmsCodeMapper();
+ }
+
+ /**
+ * Checks different variations for AVS codes mapping.
+ *
+ * @covers \Magento\Braintree\Model\AvsEmsCodeMapper::getCode
+ * @param string $avsZip
+ * @param string $avsStreet
+ * @param string $expected
+ * @dataProvider getCodeDataProvider
+ */
+ public function testGetCode($avsZip, $avsStreet, $expected)
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::once())
+ ->method('getMethod')
+ ->willReturn(ConfigProvider::CODE);
+
+ $orderPayment->expects(self::once())
+ ->method('getAdditionalInformation')
+ ->willReturn([
+ 'avsPostalCodeResponseCode' => $avsZip,
+ 'avsStreetAddressResponseCode' => $avsStreet
+ ]);
+
+ self::assertEquals($expected, $this->mapper->getCode($orderPayment));
+ }
+
+ /**
+ * Checks a test case, when payment order is not Braintree payment method.
+ *
+ * @covers \Magento\Braintree\Model\AvsEmsCodeMapper::getCode
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The "some_payment" does not supported by Braintree AVS mapper.
+ */
+ public function testGetCodeWithException()
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::exactly(2))
+ ->method('getMethod')
+ ->willReturn('some_payment');
+
+ $this->mapper->getCode($orderPayment);
+ }
+
+ /**
+ * Gets list of AVS codes.
+ *
+ * @return array
+ */
+ 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' => 'M', 'avsStreet' => 'M', 'expected' => 'Y'],
+ ['avsZip' => 'N', 'avsStreet' => 'M', 'expected' => 'A'],
+ ['avsZip' => 'M', 'avsStreet' => 'N', 'expected' => 'Z'],
+ ['avsZip' => 'N', 'avsStreet' => 'N', 'expected' => 'N'],
+ ['avsZip' => 'U', 'avsStreet' => 'U', 'expected' => 'U'],
+ ['avsZip' => 'I', 'avsStreet' => 'I', 'expected' => 'U'],
+ ['avsZip' => 'A', 'avsStreet' => 'A', 'expected' => 'E'],
+ ];
+ }
+}
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/CvvEmsCodeMapperTest.php b/app/code/Magento/Braintree/Test/Unit/Model/CvvEmsCodeMapperTest.php
new file mode 100644
index 0000000000000..fd54f9e751012
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Unit/Model/CvvEmsCodeMapperTest.php
@@ -0,0 +1,94 @@
+mapper = new CvvEmsCodeMapper();
+ }
+
+ /**
+ * Checks different variations for cvv codes mapping.
+ *
+ * @covers \Magento\Braintree\Model\CvvEmsCodeMapper::getCode
+ * @param string $cvvCode
+ * @param string $expected
+ * @dataProvider getCodeDataProvider
+ */
+ public function testGetCode($cvvCode, $expected)
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::once())
+ ->method('getMethod')
+ ->willReturn(ConfigProvider::CODE);
+
+ $orderPayment->expects(self::once())
+ ->method('getAdditionalInformation')
+ ->willReturn(['cvvResponseCode' => $cvvCode]);
+
+ self::assertEquals($expected, $this->mapper->getCode($orderPayment));
+ }
+
+ /**
+ * Checks a test case, when payment order is not Braintree payment method.
+ *
+ * @covers \Magento\Braintree\Model\CvvEmsCodeMapper::getCode
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The "some_payment" does not supported by Braintree CVV mapper.
+ */
+ public function testGetCodeWithException()
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::exactly(2))
+ ->method('getMethod')
+ ->willReturn('some_payment');
+
+ $this->mapper->getCode($orderPayment);
+ }
+
+ /**
+ * Gets variations of cvv codes and expected mapping result.
+ *
+ * @return array
+ */
+ public function getCodeDataProvider()
+ {
+ return [
+ ['cvvCode' => '', 'expected' => 'P'],
+ ['cvvCode' => null, 'expected' => 'P'],
+ ['cvvCode' => 'Unknown', 'expected' => 'P'],
+ ['cvvCode' => 'M', 'expected' => 'M'],
+ ['cvvCode' => 'N', 'expected' => 'N'],
+ ['cvvCode' => 'U', 'expected' => 'P'],
+ ['cvvCode' => 'I', 'expected' => 'P'],
+ ['cvvCode' => 'S', 'expected' => 'S'],
+ ['cvvCode' => 'A', 'expected' => ''],
+ ];
+ }
+}
diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml
index eaa233da109ce..43328406e2012 100644
--- a/app/code/Magento/Braintree/etc/config.xml
+++ b/app/code/Magento/Braintree/etc/config.xml
@@ -40,6 +40,8 @@
cvv,number
avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision
cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision
+ Magento\Braintree\Model\AvsEmsCodeMapper
+ Magento\Braintree\Model\CvvEmsCodeMapper
BraintreePayPalFacade
diff --git a/app/code/Magento/Payment/Api/PaymentVerificationInterface.php b/app/code/Magento/Payment/Api/PaymentVerificationInterface.php
new file mode 100644
index 0000000000000..f5c05080cfe9c
--- /dev/null
+++ b/app/code/Magento/Payment/Api/PaymentVerificationInterface.php
@@ -0,0 +1,34 @@
+ 'Y',
+ 'NY' => 'A',
+ 'YN' => 'Z',
+ 'NN' => 'N'
+ ];
+
+ /**
+ * Gets payment AVS verification code.
+ *
+ * @param OrderPaymentInterface $orderPayment
+ * @return string
+ * @throws \InvalidArgumentException If specified order payment has different payment method code.
+ */
+ public function getCode(OrderPaymentInterface $orderPayment)
+ {
+ if ($orderPayment->getMethod() !== Config::METHOD_PAYFLOWPRO) {
+ throw new \InvalidArgumentException(
+ 'The "' . $orderPayment->getMethod() . '" does not supported by Payflow AVS mapper.'
+ );
+ }
+
+ $additionalInfo = $orderPayment->getAdditionalInformation();
+ if (empty($additionalInfo[Info::PAYPAL_AVSADDR]) ||
+ empty($additionalInfo[Info::PAYPAL_AVSZIP])
+ ) {
+ return self::$unavailableCode;
+ }
+
+ $streetCode = $additionalInfo[Info::PAYPAL_AVSADDR];
+ $zipCode = $additionalInfo[Info::PAYPAL_AVSZIP];
+ $key = $zipCode . $streetCode;
+
+ return isset(self::$avsMap[$key]) ? self::$avsMap[$key] : self::$unavailableCode;
+ }
+}
diff --git a/app/code/Magento/Paypal/Model/Payflow/CvvEmsCodeMapper.php b/app/code/Magento/Paypal/Model/Payflow/CvvEmsCodeMapper.php
new file mode 100644
index 0000000000000..214881504b4c3
--- /dev/null
+++ b/app/code/Magento/Paypal/Model/Payflow/CvvEmsCodeMapper.php
@@ -0,0 +1,63 @@
+ 'M',
+ 'N' => 'N'
+ ];
+
+ /**
+ * Gets payment CVV verification code.
+ *
+ * @param OrderPaymentInterface $orderPayment
+ * @return string
+ * @throws \InvalidArgumentException If specified order payment has different payment method code.
+ */
+ public function getCode(OrderPaymentInterface $orderPayment)
+ {
+ if ($orderPayment->getMethod() !== Config::METHOD_PAYFLOWPRO) {
+ throw new \InvalidArgumentException(
+ 'The "' . $orderPayment->getMethod() . '" does not supported by Payflow CVV mapper.'
+ );
+ }
+
+ $additionalInfo = $orderPayment->getAdditionalInformation();
+ if (empty($additionalInfo[Info::PAYPAL_CVV2MATCH])) {
+ return self::$notProvidedCode;
+ }
+
+ $cvv = $additionalInfo[Info::PAYPAL_CVV2MATCH];
+
+ return isset(self::$cvvMap[$cvv]) ? self::$cvvMap[$cvv] : self::$notProvidedCode;
+ }
+}
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php
new file mode 100644
index 0000000000000..7051903150ba7
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php
@@ -0,0 +1,101 @@
+mapper = new AvsEmsCodeMapper();
+ }
+
+ /**
+ * Checks different variations for AVS codes mapping.
+ *
+ * @covers \Magento\Paypal\Model\Payflow\AvsEmsCodeMapper::getCode
+ * @param string $avsZip
+ * @param string $avsStreet
+ * @param string $expected
+ * @dataProvider getCodeDataProvider
+ */
+ public function testGetCode($avsZip, $avsStreet, $expected)
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::once())
+ ->method('getMethod')
+ ->willReturn(Config::METHOD_PAYFLOWPRO);
+
+ $orderPayment->expects(self::once())
+ ->method('getAdditionalInformation')
+ ->willReturn([
+ Info::PAYPAL_AVSZIP => $avsZip,
+ Info::PAYPAL_AVSADDR => $avsStreet
+ ]);
+
+ self::assertEquals($expected, $this->mapper->getCode($orderPayment));
+ }
+
+ /**
+ * Checks a test case, when payment order is not Payflow payment method.
+ *
+ * @covers \Magento\Paypal\Model\Payflow\AvsEmsCodeMapper::getCode
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The "some_payment" does not supported by Payflow AVS mapper.
+ */
+ public function testGetCodeWithException()
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::exactly(2))
+ ->method('getMethod')
+ ->willReturn('some_payment');
+
+ $this->mapper->getCode($orderPayment);
+ }
+
+ /**
+ * Gets list of AVS codes.
+ *
+ * @return array
+ */
+ public function getCodeDataProvider()
+ {
+ return [
+ ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'],
+ ['avsZip' => null, 'avsStreet' => 'Y', 'expected' => 'U'],
+ ['avsZip' => 'Y', 'avsStreet' => null, 'expected' => 'U'],
+ ['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']
+ ];
+ }
+}
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/CvvEmsCodeMapperTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/CvvEmsCodeMapperTest.php
new file mode 100644
index 0000000000000..b07c6b4e3f936
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/CvvEmsCodeMapperTest.php
@@ -0,0 +1,91 @@
+mapper = new CvvEmsCodeMapper();
+ }
+
+ /**
+ * Checks different variations for cvv codes mapping.
+ *
+ * @covers \Magento\Paypal\Model\Payflow\CvvEmsCodeMapper::getCode
+ * @param string $cvvCode
+ * @param string $expected
+ * @dataProvider getCodeDataProvider
+ */
+ public function testGetCode($cvvCode, $expected)
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::once())
+ ->method('getMethod')
+ ->willReturn(Config::METHOD_PAYFLOWPRO);
+
+ $orderPayment->expects(self::once())
+ ->method('getAdditionalInformation')
+ ->willReturn([Info::PAYPAL_CVV2MATCH => $cvvCode]);
+
+ self::assertEquals($expected, $this->mapper->getCode($orderPayment));
+ }
+
+ /**
+ * Checks a test case, when payment order is not Payflow payment method.
+ *
+ * @covers \Magento\Paypal\Model\Payflow\CvvEmsCodeMapper::getCode
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The "some_payment" does not supported by Payflow CVV mapper.
+ */
+ public function testGetCodeWithException()
+ {
+ /** @var OrderPaymentInterface|MockObject $orderPayment */
+ $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $orderPayment->expects(self::exactly(2))
+ ->method('getMethod')
+ ->willReturn('some_payment');
+
+ $this->mapper->getCode($orderPayment);
+ }
+
+ /**
+ * Gets variations of cvv codes and expected mapping result.
+ *
+ * @return array
+ */
+ public function getCodeDataProvider()
+ {
+ return [
+ ['cvvCode' => '', 'expected' => 'P'],
+ ['cvvCode' => null, 'expected' => 'P'],
+ ['cvvCode' => 'Y', 'expected' => 'M'],
+ ['cvvCode' => 'N', 'expected' => 'N'],
+ ['cvvCode' => 'X', 'expected' => 'P']
+ ];
+ }
+}
diff --git a/app/code/Magento/Paypal/etc/config.xml b/app/code/Magento/Paypal/etc/config.xml
index f72b9324beeb5..442c87ad840ca 100644
--- a/app/code/Magento/Paypal/etc/config.xml
+++ b/app/code/Magento/Paypal/etc/config.xml
@@ -91,6 +91,8 @@
2
1
1
+ Magento\Paypal\Model\Payflow\AvsEmsCodeMapper
+ Magento\Paypal\Model\Payflow\CvvEmsCodeMapper
PayflowProCreditCardVaultFacade
diff --git a/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php b/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php
index 10cbb175c70bc..9da95269ca418 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/AbstractGrid.php
@@ -67,6 +67,7 @@ public function getGridTable()
{
return $this->getTable($this->gridTableName);
}
+
/**
* Purge grid row
*
@@ -89,6 +90,9 @@ public function purge($value, $field = null)
*
* @param string $default
* @return string
+ * @deprecated this method is not used in abstract model but only in single child so
+ * this deprecation is a part of cleaning abstract classes.
+ * @see \Magento\Sales\Model\ResourceModel\Provider\UpdatedIdListProvider
*/
protected function getLastUpdatedAtValue($default = '0000-00-00 00:00:00')
{
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Grid.php b/app/code/Magento/Sales/Model/ResourceModel/Grid.php
index ba3419d0cd96e..f537074ad66f1 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Grid.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Grid.php
@@ -5,9 +5,10 @@
*/
namespace Magento\Sales\Model\ResourceModel;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Adapter\AdapterInterface;
-use Magento\Sales\Model\ResourceModel\AbstractGrid;
use Magento\Framework\Model\ResourceModel\Db\Context;
+use Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProviderInterface;
/**
* Class Grid
@@ -39,6 +40,11 @@ class Grid extends AbstractGrid
*/
protected $columns;
+ /**
+ * @var NotSyncedDataProviderInterface
+ */
+ private $notSyncedDataProvider;
+
/**
* @param Context $context
* @param string $mainTableName
@@ -47,6 +53,7 @@ class Grid extends AbstractGrid
* @param array $joins
* @param array $columns
* @param string $connectionName
+ * @param NotSyncedDataProviderInterface $notSyncedDataProvider
*/
public function __construct(
Context $context,
@@ -55,13 +62,16 @@ public function __construct(
$orderIdField,
array $joins = [],
array $columns = [],
- $connectionName = null
+ $connectionName = null,
+ NotSyncedDataProviderInterface $notSyncedDataProvider = null
) {
$this->mainTableName = $mainTableName;
$this->gridTableName = $gridTableName;
$this->orderIdField = $orderIdField;
$this->joins = $joins;
$this->columns = $columns;
+ $this->notSyncedDataProvider =
+ $notSyncedDataProvider ?: ObjectManager::getInstance()->get(NotSyncedDataProviderInterface::class);
parent::__construct($context, $connectionName);
}
@@ -99,7 +109,10 @@ public function refresh($value, $field = null)
public function refreshBySchedule()
{
$select = $this->getGridOriginSelect()
- ->where($this->mainTableName . '.updated_at >= ?', $this->getLastUpdatedAtValue());
+ ->where(
+ $this->mainTableName . '.entity_id IN (?)',
+ $this->notSyncedDataProvider->getIds($this->mainTableName, $this->gridTableName)
+ );
return $this->getConnection()->query(
$this->getConnection()
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php
new file mode 100644
index 0000000000000..8215eaaf0ad2e
--- /dev/null
+++ b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProvider.php
@@ -0,0 +1,48 @@
+providers = $tmapFactory->create(
+ [
+ 'array' => $providers,
+ 'type' => NotSyncedDataProviderInterface::class
+ ]
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getIds($mainTableName, $gridTableName)
+ {
+ $result = [];
+ foreach ($this->providers as $provider) {
+ $result = array_merge($result, $provider->getIds($mainTableName, $gridTableName));
+ }
+
+ return array_unique($result);
+ }
+}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProviderInterface.php b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProviderInterface.php
new file mode 100644
index 0000000000000..489e771d968b5
--- /dev/null
+++ b/app/code/Magento/Sales/Model/ResourceModel/Provider/NotSyncedDataProviderInterface.php
@@ -0,0 +1,21 @@
+resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getIds($mainTableName, $gridTableName)
+ {
+ $select = $this->getConnection()->select()
+ ->from($this->getConnection()->getTableName($mainTableName), [$mainTableName.'.entity_id'])
+ ->joinLeft(
+ [$gridTableName => $this->getConnection()->getTableName($gridTableName)],
+ sprintf(
+ '%s.%s = %s.%s',
+ $mainTableName,
+ 'entity_id',
+ $gridTableName,
+ 'entity_id'
+ ),
+ []
+ )
+ ->where($gridTableName.'.entity_id IS NULL');
+
+ return $this->getConnection()->fetchAll($select, [], \Zend_Db::FETCH_COLUMN);
+ }
+
+ /**
+ * Returns connection.
+ *
+ * @return AdapterInterface
+ */
+ private function getConnection()
+ {
+ if (!$this->connection) {
+ $this->connection = $this->resourceConnection->getConnection('sales');
+ }
+
+ return $this->connection;
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Provider/NotSyncedDataProviderTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Provider/NotSyncedDataProviderTest.php
new file mode 100644
index 0000000000000..92a40e2638fe8
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Provider/NotSyncedDataProviderTest.php
@@ -0,0 +1,99 @@
+getMockBuilder(TMapFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $tMap = $this->getMockBuilder(TMap::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $tMapFactory->expects(static::once())
+ ->method('create')
+ ->with(
+ [
+ 'array' => [],
+ 'type' => NotSyncedDataProviderInterface::class
+ ]
+ )
+ ->willReturn($tMap);
+ $tMap->expects(static::once())
+ ->method('getIterator')
+ ->willReturn(new \ArrayIterator([]));
+
+ $provider = new NotSyncedDataProvider($tMapFactory, []);
+ static::assertEquals([], $provider->getIds('main_table', 'grid_table'));
+ }
+
+ /**
+ * @covers \Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProvider::getIds
+ */
+ public function testGetIds()
+ {
+ /** @var TMapFactory|MockObject $tMapFactory */
+ $tMapFactory = $this->getMockBuilder(TMapFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $tMap = $this->getMockBuilder(TMap::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $provider1 = $this->getMockBuilder(NotSyncedDataProviderInterface::class)
+ ->getMockForAbstractClass();
+ $provider1->expects(static::once())
+ ->method('getIds')
+ ->willReturn([1, 2]);
+
+ $provider2 = $this->getMockBuilder(NotSyncedDataProviderInterface::class)
+ ->getMockForAbstractClass();
+ $provider2->expects(static::once())
+ ->method('getIds')
+ ->willReturn([2, 3, 4]);
+
+ $tMapFactory->expects(static::once())
+ ->method('create')
+ ->with(
+ [
+ 'array' => [
+ 'provider1' => NotSyncedDataProviderInterface::class,
+ 'provider2' => NotSyncedDataProviderInterface::class
+ ],
+ 'type' => NotSyncedDataProviderInterface::class
+ ]
+ )
+ ->willReturn($tMap);
+ $tMap->expects(static::once())
+ ->method('getIterator')
+ ->willReturn(new \ArrayIterator([$provider1, $provider2]));
+
+ $provider = new NotSyncedDataProvider(
+ $tMapFactory,
+ [
+ 'provider1' => NotSyncedDataProviderInterface::class,
+ 'provider2' => NotSyncedDataProviderInterface::class,
+ ]
+ );
+
+ static::assertEquals([1, 2, 3, 4], array_values($provider->getIds('main_table', 'grid_table')));
+ }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 91e5f07e7f61a..0f14946c89a42 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -114,6 +114,14 @@
+
+
+
+
+ - Magento\Sales\Model\ResourceModel\Provider\UpdatedIdListProvider
+
+
+
diff --git a/app/design/adminhtml/Magento/backend/Magento_Signifyd/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Signifyd/web/css/source/_module.less
index 7449dce5e2ebb..994a26c12095c 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Signifyd/web/css/source/_module.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Signifyd/web/css/source/_module.less
@@ -11,3 +11,13 @@
&:extend(.abs-order-tables all);
&:extend(.abs-order-tbody-border all);
}
+
+//
+// Layout
+// ---------------------------------------------
+.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
+ .case-information {
+ float: left;
+ #mix-grid .width(6,12);
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertAuthorizationInCommentsHistory.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertAuthorizationInCommentsHistory.php
index 78e5a3b31c4cd..898fba0c32f6a 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertAuthorizationInCommentsHistory.php
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertAuthorizationInCommentsHistory.php
@@ -40,11 +40,12 @@ public function processAssert(
/** @var \Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Info $infoTab */
$infoTab = $salesOrderView->getOrderForm()->openTab('info')->getTab('info');
- $latestComment = $infoTab->getCommentsHistoryBlock()->getLatestComment();
+ $orderComments = $infoTab->getCommentsHistoryBlock()->getComments();
+ $commentsMessages = array_column($orderComments, 'comment');
\PHPUnit_Framework_Assert::assertRegExp(
sprintf(self::AUTHORIZED_AMOUNT_PATTERN, $prices['grandTotal']),
- $latestComment['comment'],
+ implode('. ', $commentsMessages),
'Incorrect authorized amount value for the order #' . $orderId
);
}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertCancelInCommentsHistory.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertCancelInCommentsHistory.php
new file mode 100644
index 0000000000000..56a9b02265da4
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertCancelInCommentsHistory.php
@@ -0,0 +1,60 @@
+open();
+ $salesOrder->getSalesOrderGrid()->searchAndOpen(['id' => $orderId]);
+
+ /** @var \Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Info $infoTab */
+ $infoTab = $salesOrderView->getOrderForm()->openTab('info')->getTab('info');
+ $comments = $infoTab->getCommentsHistoryBlock()->getComments();
+ $commentsMessages = array_column($comments, 'comment');
+
+ \PHPUnit_Framework_Assert::assertRegExp(
+ sprintf($this->canceledAmountPattern, $prices['grandTotal']),
+ implode('. ', $commentsMessages),
+ 'Incorrect canceled amount value for the order #' . $orderId
+ );
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return "Message about canceled amount is available in Comments History section.";
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CancelOrderStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CancelOrderStep.php
new file mode 100644
index 0000000000000..deee46aa79a3e
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/CancelOrderStep.php
@@ -0,0 +1,65 @@
+orderIndex = $orderIndex;
+ $this->order = $order;
+ $this->salesOrderView = $salesOrderView;
+ }
+
+ /**
+ * Run step flow
+ *
+ * @return void
+ */
+ public function run()
+ {
+ $this->orderIndex->open();
+ $this->orderIndex->getSalesOrderGrid()->searchAndOpen(['id' => $this->order->getId()]);
+ $this->salesOrderView->getPageActions()->cancel();
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
index b6c087e8edba9..3b497fa10d654 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/di.xml
@@ -116,4 +116,9 @@
S1
+
+
+ S1
+
+