Skip to content

Commit ffac6ee

Browse files
authored
Merge pull request #2496 from magento-mpi/MPI-PR
[MPI] Bug fixes
2 parents 922b391 + ff7ef11 commit ffac6ee

File tree

27 files changed

+1161
-252
lines changed

27 files changed

+1161
-252
lines changed
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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
* This validator decorates the general response validator to handle specific cases like
19+
* an expired or already voided on Braintree side authorization transaction.
20+
*/
21+
class CancelResponseValidator extends AbstractValidator
22+
{
23+
/**
24+
* @var int
25+
*/
26+
private static $acceptableTransactionCode = 91504;
27+
28+
/**
29+
* @var GeneralResponseValidator
30+
*/
31+
private $generalResponseValidator;
32+
33+
/**
34+
* @var SubjectReader
35+
*/
36+
private $subjectReader;
37+
38+
/**
39+
* @param ResultInterfaceFactory $resultFactory
40+
* @param GeneralResponseValidator $generalResponseValidator
41+
*/
42+
public function __construct(
43+
ResultInterfaceFactory $resultFactory,
44+
GeneralResponseValidator $generalResponseValidator,
45+
SubjectReader $subjectReader
46+
) {
47+
parent::__construct($resultFactory);
48+
$this->generalResponseValidator = $generalResponseValidator;
49+
$this->subjectReader = $subjectReader;
50+
}
51+
52+
/**
53+
* @inheritdoc
54+
*/
55+
public function validate(array $validationSubject): ResultInterface
56+
{
57+
$result = $this->generalResponseValidator->validate($validationSubject);
58+
if (!$result->isValid()) {
59+
$response = $this->subjectReader->readResponseObject($validationSubject);
60+
if ($this->isErrorAcceptable($response->errors)) {
61+
$result = $this->createResult(true, [__('Transaction is cancelled offline.')]);
62+
}
63+
}
64+
65+
return $result;
66+
}
67+
68+
/**
69+
* Checks if error collection has an acceptable error code.
70+
*
71+
* @param ErrorCollection $errorCollection
72+
* @return bool
73+
*/
74+
private function isErrorAcceptable(ErrorCollection $errorCollection): bool
75+
{
76+
$errors = $errorCollection->deepAll();
77+
// there is should be only one acceptable error
78+
if (count($errors) > 1) {
79+
return false;
80+
}
81+
82+
/** @var Validation $error */
83+
$error = array_pop($errors);
84+
return (int)$error->code === self::$acceptableTransactionCode;
85+
}
86+
}

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

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
class CancelDetailsHandlerTest extends TestCase
19+
{
20+
/**
21+
* @var CancelDetailsHandler
22+
*/
23+
private $handler;
24+
25+
protected function setUp()
26+
{
27+
$this->handler = new CancelDetailsHandler(new SubjectReader());
28+
}
29+
30+
/**
31+
* Checks a case when cancel handler closes the current and parent transactions.
32+
*/
33+
public function testHandle()
34+
{
35+
/** @var OrderAdapterInterface|MockObject $order */
36+
$order = $this->getMockForAbstractClass(OrderAdapterInterface::class);
37+
/** @var Payment|MockObject $payment */
38+
$payment = $this->getMockBuilder(Payment::class)
39+
->disableOriginalConstructor()
40+
->setMethods(['setOrder'])
41+
->getMock();
42+
43+
$paymentDO = new PaymentDataObject($order, $payment);
44+
$response = [
45+
'payment' => $paymentDO
46+
];
47+
48+
$this->handler->handle($response, []);
49+
50+
self::assertTrue($payment->getIsTransactionClosed(), 'The current transaction should be closed.');
51+
self::assertTrue($payment->getShouldCloseParentTransaction(), 'The parent transaction should be closed.');
52+
}
53+
}

0 commit comments

Comments
 (0)