Skip to content

Commit 02aaccb

Browse files
author
Alexander Akimov
authored
Merge pull request #2681 from magento-tsg/2.3-develop-pr22
[TSG] Upporting for 2.3 (pr22) (2.3.0)
2 parents 55256fa + 5cfc987 commit 02aaccb

File tree

48 files changed

+1627
-858
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1627
-858
lines changed

app/code/Magento/Backend/Block/GlobalSearch.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function getWidgetInitOptions()
3131
'filterProperty' => 'name',
3232
'preventClickPropagation' => false,
3333
'minLength' => 2,
34+
'submitInputOnEnter' => false,
3435
]
3536
];
3637
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Braintree\Gateway\Response;
9+
10+
use Magento\Braintree\Gateway\SubjectReader;
11+
use Magento\Payment\Gateway\Response\HandlerInterface;
12+
use Magento\Sales\Model\Order\Payment;
13+
14+
/**
15+
* Handles response details for order cancellation request.
16+
*/
17+
class CancelDetailsHandler implements HandlerInterface
18+
{
19+
/**
20+
* @var SubjectReader
21+
*/
22+
private $subjectReader;
23+
24+
/**
25+
* @param SubjectReader $subjectReader
26+
*/
27+
public function __construct(SubjectReader $subjectReader)
28+
{
29+
$this->subjectReader = $subjectReader;
30+
}
31+
32+
/**
33+
* @inheritdoc
34+
*/
35+
public function handle(array $handlingSubject, array $response)
36+
{
37+
$paymentDO = $this->subjectReader->readPayment($handlingSubject);
38+
/** @var Payment $orderPayment */
39+
$orderPayment = $paymentDO->getPayment();
40+
$orderPayment->setIsTransactionClosed(true);
41+
$orderPayment->setShouldCloseParentTransaction(true);
42+
}
43+
}

app/code/Magento/Braintree/Gateway/SubjectReader.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,20 @@ public function readPayment(array $subject)
4343
}
4444

4545
/**
46-
* Reads transaction from subject
46+
* Reads transaction from the subject.
4747
*
4848
* @param array $subject
49-
* @return \Braintree\Transaction
49+
* @return Transaction
50+
* @throws \InvalidArgumentException if the subject doesn't contain transaction details.
5051
*/
5152
public function readTransaction(array $subject)
5253
{
5354
if (!isset($subject['object']) || !is_object($subject['object'])) {
54-
throw new \InvalidArgumentException('Response object does not exist');
55+
throw new \InvalidArgumentException('Response object does not exist.');
5556
}
5657

5758
if (!isset($subject['object']->transaction)
58-
&& !$subject['object']->transaction instanceof Transaction
59+
|| !$subject['object']->transaction instanceof Transaction
5960
) {
6061
throw new \InvalidArgumentException('The object is not a class \Braintree\Transaction.');
6162
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Braintree\Gateway\Validator;
9+
10+
use Braintree\Error\ErrorCollection;
11+
use Braintree\Error\Validation;
12+
use Magento\Payment\Gateway\Validator\AbstractValidator;
13+
use Magento\Payment\Gateway\Validator\ResultInterface;
14+
use Magento\Payment\Gateway\Validator\ResultInterfaceFactory;
15+
use Magento\Braintree\Gateway\SubjectReader;
16+
17+
/**
18+
* Decorates the general response validator to handle specific cases.
19+
*
20+
* This validator decorates the general response validator to handle specific cases like
21+
* an expired or already voided on Braintree side authorization transaction.
22+
*/
23+
class CancelResponseValidator extends AbstractValidator
24+
{
25+
/**
26+
* @var int
27+
*/
28+
private static $acceptableTransactionCode = 91504;
29+
30+
/**
31+
* @var GeneralResponseValidator
32+
*/
33+
private $generalResponseValidator;
34+
35+
/**
36+
* @var SubjectReader
37+
*/
38+
private $subjectReader;
39+
40+
/**
41+
* @param ResultInterfaceFactory $resultFactory
42+
* @param GeneralResponseValidator $generalResponseValidator
43+
* @param SubjectReader $subjectReader
44+
*/
45+
public function __construct(
46+
ResultInterfaceFactory $resultFactory,
47+
GeneralResponseValidator $generalResponseValidator,
48+
SubjectReader $subjectReader
49+
) {
50+
parent::__construct($resultFactory);
51+
$this->generalResponseValidator = $generalResponseValidator;
52+
$this->subjectReader = $subjectReader;
53+
}
54+
55+
/**
56+
* @inheritdoc
57+
*/
58+
public function validate(array $validationSubject): ResultInterface
59+
{
60+
$result = $this->generalResponseValidator->validate($validationSubject);
61+
if (!$result->isValid()) {
62+
$response = $this->subjectReader->readResponseObject($validationSubject);
63+
if ($this->isErrorAcceptable($response->errors)) {
64+
$result = $this->createResult(true, [__('Transaction is cancelled offline.')]);
65+
}
66+
}
67+
68+
return $result;
69+
}
70+
71+
/**
72+
* Checks if error collection has an acceptable error code.
73+
*
74+
* @param ErrorCollection $errorCollection
75+
* @return bool
76+
*/
77+
private function isErrorAcceptable(ErrorCollection $errorCollection): bool
78+
{
79+
$errors = $errorCollection->deepAll();
80+
// there is should be only one acceptable error
81+
if (count($errors) > 1) {
82+
return false;
83+
}
84+
85+
/** @var Validation $error */
86+
$error = array_pop($errors);
87+
88+
return (int)$error->code === self::$acceptableTransactionCode;
89+
}
90+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Braintree\Test\Unit\Gateway\Response;
9+
10+
use Magento\Braintree\Gateway\Response\CancelDetailsHandler;
11+
use Magento\Braintree\Gateway\SubjectReader;
12+
use Magento\Payment\Gateway\Data\OrderAdapterInterface;
13+
use Magento\Payment\Gateway\Data\PaymentDataObject;
14+
use Magento\Sales\Model\Order\Payment;
15+
use PHPUnit\Framework\TestCase;
16+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
17+
18+
/**
19+
* Tests \Magento\Braintree\Gateway\Response\CancelDetailsHandler.
20+
*/
21+
class CancelDetailsHandlerTest extends TestCase
22+
{
23+
/**
24+
* @var CancelDetailsHandler
25+
*/
26+
private $handler;
27+
28+
/**
29+
* @inheritdoc
30+
*/
31+
protected function setUp()
32+
{
33+
$this->handler = new CancelDetailsHandler(new SubjectReader());
34+
}
35+
36+
/**
37+
* Checks a case when cancel handler closes the current and parent transactions.
38+
*
39+
* @return void
40+
*/
41+
public function testHandle(): void
42+
{
43+
/** @var OrderAdapterInterface|MockObject $order */
44+
$order = $this->getMockForAbstractClass(OrderAdapterInterface::class);
45+
/** @var Payment|MockObject $payment */
46+
$payment = $this->getMockBuilder(Payment::class)
47+
->disableOriginalConstructor()
48+
->setMethods(['setOrder'])
49+
->getMock();
50+
51+
$paymentDO = new PaymentDataObject($order, $payment);
52+
$response = [
53+
'payment' => $paymentDO,
54+
];
55+
56+
$this->handler->handle($response, []);
57+
58+
self::assertTrue($payment->getIsTransactionClosed(), 'The current transaction should be closed.');
59+
self::assertTrue($payment->getShouldCloseParentTransaction(), 'The parent transaction should be closed.');
60+
}
61+
}

app/code/Magento/Braintree/Test/Unit/Gateway/SubjectReaderTest.php

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\Braintree\Test\Unit\Gateway;
77

8+
use Braintree\Result\Successful;
89
use Braintree\Transaction;
910
use Magento\Braintree\Gateway\SubjectReader;
1011

@@ -18,6 +19,9 @@ class SubjectReaderTest extends \PHPUnit\Framework\TestCase
1819
*/
1920
private $subjectReader;
2021

22+
/**
23+
* @inheritdoc
24+
*/
2125
protected function setUp()
2226
{
2327
$this->subjectReader = new SubjectReader();
@@ -27,67 +31,137 @@ protected function setUp()
2731
* @covers \Magento\Braintree\Gateway\SubjectReader::readCustomerId
2832
* @expectedException \InvalidArgumentException
2933
* @expectedExceptionMessage The "customerId" field does not exists
34+
* @return void
3035
*/
31-
public function testReadCustomerIdWithException()
36+
public function testReadCustomerIdWithException(): void
3237
{
3338
$this->subjectReader->readCustomerId([]);
3439
}
3540

3641
/**
3742
* @covers \Magento\Braintree\Gateway\SubjectReader::readCustomerId
43+
* @return void
3844
*/
39-
public function testReadCustomerId()
45+
public function testReadCustomerId(): void
4046
{
4147
$customerId = 1;
42-
static::assertEquals($customerId, $this->subjectReader->readCustomerId(['customer_id' => $customerId]));
48+
$this->assertEquals($customerId, $this->subjectReader->readCustomerId(['customer_id' => $customerId]));
4349
}
4450

4551
/**
4652
* @covers \Magento\Braintree\Gateway\SubjectReader::readPublicHash
4753
* @expectedException \InvalidArgumentException
4854
* @expectedExceptionMessage The "public_hash" field does not exists
55+
* @return void
4956
*/
50-
public function testReadPublicHashWithException()
57+
public function testReadPublicHashWithException(): void
5158
{
5259
$this->subjectReader->readPublicHash([]);
5360
}
5461

5562
/**
5663
* @covers \Magento\Braintree\Gateway\SubjectReader::readPublicHash
64+
* @return void
5765
*/
58-
public function testReadPublicHash()
66+
public function testReadPublicHash(): void
5967
{
6068
$hash = 'fj23djf2o1fd';
61-
static::assertEquals($hash, $this->subjectReader->readPublicHash(['public_hash' => $hash]));
69+
$this->assertEquals($hash, $this->subjectReader->readPublicHash(['public_hash' => $hash]));
6270
}
6371

6472
/**
6573
* @covers \Magento\Braintree\Gateway\SubjectReader::readPayPal
6674
* @expectedException \InvalidArgumentException
6775
* @expectedExceptionMessage Transaction has't paypal attribute
76+
* @return void
6877
*/
69-
public function testReadPayPalWithException()
78+
public function testReadPayPalWithException(): void
7079
{
7180
$transaction = Transaction::factory([
72-
'id' => 'u38rf8kg6vn'
81+
'id' => 'u38rf8kg6vn',
7382
]);
7483
$this->subjectReader->readPayPal($transaction);
7584
}
7685

7786
/**
7887
* @covers \Magento\Braintree\Gateway\SubjectReader::readPayPal
88+
* @return void
7989
*/
80-
public function testReadPayPal()
90+
public function testReadPayPal(): void
8191
{
8292
$paypal = [
8393
'paymentId' => '3ek7dk7fn0vi1',
84-
'payerEmail' => '[email protected]'
94+
'payerEmail' => '[email protected]',
8595
];
8696
$transaction = Transaction::factory([
8797
'id' => '4yr95vb',
88-
'paypal' => $paypal
98+
'paypal' => $paypal,
8999
]);
90100

91-
static::assertEquals($paypal, $this->subjectReader->readPayPal($transaction));
101+
$this->assertEquals($paypal, $this->subjectReader->readPayPal($transaction));
102+
}
103+
104+
/**
105+
* Checks a case when subject reader retrieves successful Braintree transaction.
106+
*
107+
* @return void
108+
*/
109+
public function testReadTransaction(): void
110+
{
111+
$transaction = Transaction::factory(['id' => 1]);
112+
$response = [
113+
'object' => new Successful($transaction, 'transaction'),
114+
];
115+
$actual = $this->subjectReader->readTransaction($response);
116+
117+
$this->assertSame($transaction, $actual);
118+
}
119+
120+
/**
121+
* Checks a case when subject reader retrieves invalid data instead transaction details.
122+
*
123+
* @param array $response
124+
* @param string $expectedMessage
125+
* @dataProvider invalidTransactionResponseDataProvider
126+
* @expectedException \InvalidArgumentException
127+
* @return void
128+
*/
129+
public function testReadTransactionWithInvalidResponse(array $response, string $expectedMessage): void
130+
{
131+
$this->expectExceptionMessage($expectedMessage);
132+
$this->subjectReader->readTransaction($response);
133+
}
134+
135+
/**
136+
* Gets list of variations with invalid subject data.
137+
*
138+
* @return array
139+
*/
140+
public function invalidTransactionResponseDataProvider(): array
141+
{
142+
$transaction = new \stdClass();
143+
$response = new \stdClass();
144+
$response->transaction = $transaction;
145+
146+
return [
147+
[
148+
'response' => [
149+
'object' => [],
150+
],
151+
'expectedMessage' => 'Response object does not exist.',
152+
],
153+
[
154+
'response' => [
155+
'object' => new \stdClass(),
156+
],
157+
'expectedMessage' => 'The object is not a class \Braintree\Transaction.',
158+
],
159+
[
160+
'response' => [
161+
'object' => $response,
162+
],
163+
'expectedMessage' => 'The object is not a class \Braintree\Transaction.',
164+
],
165+
];
92166
}
93167
}

0 commit comments

Comments
 (0)