Skip to content

Commit 94cac99

Browse files
committed
Merge remote-tracking branch 'mpi/MC-19114' into Chaika-PR-2019-08-27
2 parents 91c1ffe + c6fa6ff commit 94cac99

File tree

10 files changed

+232
-40
lines changed

10 files changed

+232
-40
lines changed

app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,45 @@ public function execute(array $commandSubject): void
5959
* @param array $commandSubject
6060
* @return string
6161
* @throws CommandException
62+
* @throws \Magento\Framework\Exception\NotFoundException
6263
*/
6364
private function getCommand(array $commandSubject): string
6465
{
6566
$details = $this->commandPool->get('get_transaction_details')
6667
->execute($commandSubject)
6768
->get();
6869

69-
if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') {
70+
if ($this->canVoid($details, $commandSubject)) {
7071
return self::VOID;
71-
} elseif ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') {
72+
}
73+
74+
if ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') {
7275
throw new CommandException(__('This transaction cannot be refunded with its current status.'));
7376
}
7477

7578
return self::REFUND;
7679
}
80+
81+
/**
82+
* Checks if void command can be performed.
83+
*
84+
* @param array $details
85+
* @param array $commandSubject
86+
* @return bool
87+
* @throws CommandException
88+
*/
89+
private function canVoid(array $details, array $commandSubject) :bool
90+
{
91+
if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') {
92+
if ((float) $details['transaction']['authAmount'] !== (float) $commandSubject['amount']) {
93+
throw new CommandException(
94+
__('The transaction has not been settled, a partial refund is not yet available.')
95+
);
96+
}
97+
98+
return true;
99+
}
100+
101+
return false;
102+
}
77103
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\AuthorizenetAcceptjs\Gateway\Response;
9+
10+
use Magento\Sales\Model\Order\Payment;
11+
12+
/**
13+
* Determines that parent transaction should be close for partial refund operation.
14+
*/
15+
class ClosePartialTransactionHandler extends CloseTransactionHandler
16+
{
17+
/**
18+
* Whether parent transaction should be closed.
19+
*
20+
* @param Payment $payment
21+
* @return bool
22+
*/
23+
public function shouldCloseParentTransaction(Payment $payment)
24+
{
25+
return !(bool)$payment->getCreditmemo()->getInvoice()->canRefund();
26+
}
27+
}

app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,19 @@ public function handle(array $handlingSubject, array $response): void
4747

4848
if ($payment instanceof Payment) {
4949
$payment->setIsTransactionClosed($this->closeTransaction);
50-
$payment->setShouldCloseParentTransaction(true);
50+
$payment->setShouldCloseParentTransaction($this->shouldCloseParentTransaction($payment));
5151
}
5252
}
53+
54+
/**
55+
* Whether parent transaction should be closed.
56+
*
57+
* @param Payment $payment
58+
* @return bool
59+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
60+
*/
61+
public function shouldCloseParentTransaction(Payment $payment)
62+
{
63+
return true;
64+
}
5365
}

app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,75 @@ public function testCommandWillVoidWhenTransactionIsPendingSettlement()
6262
->method('execute');
6363

6464
$this->commandPoolMock->method('get')
65-
->willReturnMap([
66-
['get_transaction_details', $this->transactionDetailsCommandMock],
67-
['void', $this->commandMock]
68-
]);
65+
->willReturnMap(
66+
[
67+
[
68+
'get_transaction_details',
69+
$this->transactionDetailsCommandMock
70+
],
71+
[
72+
'void',
73+
$this->commandMock
74+
]
75+
]
76+
);
6977

7078
$this->transactionResultMock->method('get')
71-
->willReturn([
72-
'transaction' => [
73-
'transactionStatus' => 'capturedPendingSettlement'
79+
->willReturn(
80+
[
81+
'transaction' => [
82+
'transactionStatus' => 'capturedPendingSettlement',
83+
'authAmount' => '20.19',
84+
]
7485
]
75-
]);
86+
);
7687

7788
$buildSubject = [
78-
'foo' => '123'
89+
'foo' => '123',
90+
'amount' => '20.19',
91+
];
92+
93+
$this->transactionDetailsCommandMock->expects($this->once())
94+
->method('execute')
95+
->with($buildSubject)
96+
->willReturn($this->transactionResultMock);
97+
98+
$this->command->execute($buildSubject);
99+
}
100+
101+
/**
102+
* @expectedException \Magento\Payment\Gateway\Command\CommandException
103+
* @expectedExceptionMessage The transaction has not been settled, a partial refund is not yet available.
104+
*/
105+
public function testCommandWillThrowExceptionWhenVoidTransactionIsPartial()
106+
{
107+
// Assert command is executed
108+
$this->commandMock->expects($this->never())
109+
->method('execute');
110+
111+
$this->commandPoolMock->method('get')
112+
->willReturnMap(
113+
[
114+
[
115+
'get_transaction_details',
116+
$this->transactionDetailsCommandMock
117+
],
118+
]
119+
);
120+
121+
$this->transactionResultMock->method('get')
122+
->willReturn(
123+
[
124+
'transaction' => [
125+
'transactionStatus' => 'capturedPendingSettlement',
126+
'authAmount' => '20.19',
127+
]
128+
]
129+
);
130+
131+
$buildSubject = [
132+
'foo' => '123',
133+
'amount' => '10.19',
79134
];
80135

81136
$this->transactionDetailsCommandMock->expects($this->once())
@@ -93,17 +148,27 @@ public function testCommandWillRefundWhenTransactionIsSettled()
93148
->method('execute');
94149

95150
$this->commandPoolMock->method('get')
96-
->willReturnMap([
97-
['get_transaction_details', $this->transactionDetailsCommandMock],
98-
['refund_settled', $this->commandMock]
99-
]);
151+
->willReturnMap(
152+
[
153+
[
154+
'get_transaction_details',
155+
$this->transactionDetailsCommandMock
156+
],
157+
[
158+
'refund_settled',
159+
$this->commandMock
160+
]
161+
]
162+
);
100163

101164
$this->transactionResultMock->method('get')
102-
->willReturn([
103-
'transaction' => [
104-
'transactionStatus' => 'settledSuccessfully'
165+
->willReturn(
166+
[
167+
'transaction' => [
168+
'transactionStatus' => 'settledSuccessfully'
169+
]
105170
]
106-
]);
171+
);
107172

108173
$buildSubject = [
109174
'foo' => '123'
@@ -128,16 +193,23 @@ public function testCommandWillThrowExceptionWhenTransactionIsInInvalidState()
128193
->method('execute');
129194

130195
$this->commandPoolMock->method('get')
131-
->willReturnMap([
132-
['get_transaction_details', $this->transactionDetailsCommandMock],
133-
]);
196+
->willReturnMap(
197+
[
198+
[
199+
'get_transaction_details',
200+
$this->transactionDetailsCommandMock
201+
],
202+
]
203+
);
134204

135205
$this->transactionResultMock->method('get')
136-
->willReturn([
137-
'transaction' => [
138-
'transactionStatus' => 'somethingIsWrong'
206+
->willReturn(
207+
[
208+
'transaction' => [
209+
'transactionStatus' => 'somethingIsWrong'
210+
]
139211
]
140-
]);
212+
);
141213

142214
$buildSubject = [
143215
'foo' => '123'

app/code/Magento/AuthorizenetAcceptjs/etc/config.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<can_capture_partial>0</can_capture_partial>
2525
<can_authorize>1</can_authorize>
2626
<can_refund>1</can_refund>
27+
<can_refund_partial_per_invoice>1</can_refund_partial_per_invoice>
2728
<can_capture>1</can_capture>
2829
<can_void>1</can_void>
2930
<can_accept_payment>1</can_accept_payment>

app/code/Magento/AuthorizenetAcceptjs/etc/di.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,7 @@
206206
<arguments>
207207
<argument name="handlers" xsi:type="array">
208208
<item name="transaction_id" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler</item>
209-
<item name="close_parent_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler</item>
210-
<item name="close_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler</item>
209+
<item name="close_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\ClosePartialTransactionHandler</item>
211210
</argument>
212211
</arguments>
213212
</virtualType>

app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ Authorize.net,Authorize.net
1919
"ccLast4","Last 4 Digits of Card"
2020
"There was an error while trying to process the refund.","There was an error while trying to process the refund."
2121
"This transaction cannot be refunded with its current status.","This transaction cannot be refunded with its current status."
22+
"The transaction has not been settled, a partial refund is not yet available.","The transaction has not been settled, a partial refund is not yet available."

dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
declare(strict_types=1);
88

99
use Magento\AuthorizenetAcceptjs\Gateway\Config;
10+
use Magento\Sales\Api\InvoiceRepositoryInterface;
1011
use Magento\Sales\Model\Order\Payment;
1112
use Magento\Sales\Model\OrderRepository;
13+
use Magento\Sales\Model\Service\InvoiceService;
1214
use Magento\TestFramework\Helper\Bootstrap;
1315
use Magento\Sales\Api\TransactionRepositoryInterface;
1416
use Magento\Sales\Model\Order\Payment\Transaction;
1517
use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder;
1618

19+
// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile
1720
$order = include __DIR__ . '/../_files/full_order.php';
1821

1922
$objectManager = Bootstrap::getObjectManager();
@@ -24,11 +27,32 @@
2427
$payment->setAuthorizationTransaction(false);
2528
$payment->setParentTransactionId(4321);
2629

27-
2830
/** @var OrderRepository $orderRepo */
2931
$orderRepo = $objectManager->get(OrderRepository::class);
3032
$orderRepo->save($order);
3133

34+
/** @var InvoiceService $invoiceService */
35+
$invoiceService = $objectManager->get(InvoiceService::class);
36+
$invoice = $invoiceService->prepareInvoice($order);
37+
$invoice->setIncrementId('100000001');
38+
$invoice->register();
39+
40+
/** @var InvoiceRepositoryInterface $invoiceRepository */
41+
$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class);
42+
$invoice = $invoiceRepository->save($invoice);
43+
44+
45+
/** @var \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory */
46+
$creditmemoFactory = $objectManager->get(\Magento\Sales\Model\Order\CreditmemoFactory::class);
47+
$creditmemo = $creditmemoFactory->createByInvoice($invoice, $invoice->getData());
48+
$creditmemo->setOrder($order);
49+
$creditmemo->setState(Magento\Sales\Model\Order\Creditmemo::STATE_OPEN);
50+
$creditmemo->setIncrementId('100000001');
51+
52+
/** @var \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository */
53+
$creditmemoRepository = $objectManager->get(\Magento\Sales\Api\CreditmemoRepositoryInterface::class);
54+
$creditmemoRepository->save($creditmemo);
55+
3256
/** @var TransactionBuilder $transactionBuilder */
3357
$transactionBuilder = $objectManager->create(TransactionBuilder::class);
3458
$transactionAuthorize = $transactionBuilder->setPayment($payment)

dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010

1111
use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest;
1212
use Magento\Payment\Gateway\Command\CommandPoolInterface;
13+
use Magento\Sales\Api\Data\CreditmemoInterface;
14+
use Magento\Sales\Model\Order;
1315
use Magento\Sales\Model\Order\Payment;
16+
use Magento\Sales\Model\ResourceModel\Order\Creditmemo\CollectionFactory as CreditmemoCollectionFactory;
1417

1518
class RefundSettledCommandTest extends AbstractTest
1619
{
@@ -29,6 +32,7 @@ public function testRefundSettledCommand()
2932

3033
$order = $this->getOrderWithIncrementId('100000001');
3134
$payment = $order->getPayment();
35+
$payment->setCreditmemo($this->getCreditmemo($order));
3236

3337
$paymentDO = $this->paymentFactory->create($payment);
3438

@@ -41,13 +45,35 @@ public function testRefundSettledCommand()
4145
$this->responseMock->method('getBody')
4246
->willReturn(json_encode($response));
4347

44-
$command->execute([
45-
'payment' => $paymentDO,
46-
'amount' => 100.00
47-
]);
48+
$command->execute(
49+
[
50+
'payment' => $paymentDO,
51+
'amount' => 100.00
52+
]
53+
);
4854

4955
/** @var Payment $payment */
5056
$this->assertTrue($payment->getIsTransactionClosed());
5157
$this->assertSame('5678', $payment->getTransactionId());
5258
}
59+
60+
/**
61+
* Retrieve creditmemo from order.
62+
*
63+
* @param Order $order
64+
* @return CreditmemoInterface
65+
*/
66+
private function getCreditmemo(Order $order): CreditmemoInterface
67+
{
68+
/** @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection $creditMemoCollection */
69+
$creditMemoCollection = $this->objectManager->create(CreditmemoCollectionFactory::class)->create();
70+
71+
/** @var CreditmemoInterface $creditMemo */
72+
$creditMemo = $creditMemoCollection
73+
->setOrderFilter($order)
74+
->setPageSize(1)
75+
->getFirstItem();
76+
77+
return $creditMemo;
78+
}
5379
}

0 commit comments

Comments
 (0)