Skip to content

Commit 2b63651

Browse files
committed
Merge remote-tracking branch 'origin/MC-30639' into 2.4-develop-pr11
2 parents 5c79b5a + 937b2fc commit 2b63651

File tree

4 files changed

+185
-10
lines changed

4 files changed

+185
-10
lines changed

app/code/Magento/Paypal/Model/Ipn.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ protected function _getConfig()
106106
$parameters = ['params' => [$methodCode, $order->getStoreId()]];
107107
$this->_config = $this->_configFactory->create($parameters);
108108
if (!$this->_config->isMethodActive($methodCode) || !$this->_config->isMethodAvailable()) {
109+
// phpcs:ignore Magento2.Exceptions.DirectThrow
109110
throw new Exception(sprintf('The "%s" method isn\'t available.', $methodCode));
110111
}
111112
/** @link https://cms.paypal.com/cgi-bin/marketingweb?cmd=_render-content&content_ID=
@@ -117,6 +118,7 @@ protected function _getConfig()
117118
}
118119
$receiver = $this->getRequestData('business') ?: $this->getRequestData('receiver_email');
119120
if (strtolower($merchantEmail) != strtolower($receiver)) {
121+
// phpcs:ignore Magento2.Exceptions.DirectThrow
120122
throw new Exception(
121123
sprintf(
122124
'The requested "%s" and the configured "%s" merchant emails don\'t match.',
@@ -140,6 +142,7 @@ protected function _getOrder()
140142
$incrementId = $this->getRequestData('invoice');
141143
$this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId);
142144
if (!$this->_order->getId()) {
145+
// phpcs:ignore Magento2.Exceptions.DirectThrow
143146
throw new Exception(sprintf('The "%s" order ID is incorrect. Verify the ID and try again.', $incrementId));
144147
}
145148
return $this->_order;
@@ -245,8 +248,11 @@ protected function _registerTransaction()
245248
break;
246249
// customer attempted to pay via bank account, but failed
247250
case Info::PAYMENTSTATUS_FAILED:
248-
// cancel order
249-
$this->_registerPaymentFailure();
251+
if ($this->_order->getState() === \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW) {
252+
$this->_registerPaymentDenial();
253+
} else {
254+
$this->_registerPaymentFailure();
255+
}
250256
break;
251257
// payment was obtained, but money were not captured yet
252258
case Info::PAYMENTSTATUS_PENDING:
@@ -270,6 +276,7 @@ protected function _registerTransaction()
270276
$this->_registerPaymentVoid();
271277
break;
272278
default:
279+
// phpcs:ignore Magento2.Exceptions.DirectThrow
273280
throw new Exception("The '{$paymentStatus}' payment status couldn't be handled.");
274281
}
275282
}
@@ -322,11 +329,12 @@ protected function _registerPaymentDenial()
322329
{
323330
try {
324331
$this->_importPaymentInformation();
325-
$this->_order->getPayment()
326-
->setTransactionId($this->getRequestData('txn_id'))
327-
->setNotificationResult(true)
328-
->setIsTransactionClosed(true)
329-
->deny(false);
332+
$payment = $this->_order->getPayment();
333+
$payment->setTransactionId($this->getRequestData('txn_id'));
334+
$payment->setPreparedMessage($this->_createIpnComment(''));
335+
$payment->setNotificationResult(true);
336+
$payment->setIsTransactionClosed(true);
337+
$payment->deny(false);
330338
$this->_order->save();
331339
} catch (LocalizedException $e) {
332340
if ($e->getMessage() != __('We cannot cancel this order.')) {
@@ -360,6 +368,7 @@ public function _registerPaymentPending()
360368
return;
361369
}
362370
if ('order' === $reason) {
371+
// phpcs:ignore Magento2.Exceptions.DirectThrow
363372
throw new Exception('The "order" authorizations aren\'t implemented.');
364373
}
365374
// case when was placed using PayPal standard
@@ -501,6 +510,7 @@ protected function _registerPaymentVoid()
501510

502511
/**
503512
* Map payment information from IPN to payment object
513+
*
504514
* Returns true if there were changes in information
505515
*
506516
* @return bool
@@ -537,8 +547,10 @@ protected function _importPaymentInformation()
537547

538548
// collect fraud filters
539549
$fraudFilters = [];
540-
for ($i = 1; $value = $this->getRequestData("fraud_management_pending_filters_{$i}"); $i++) {
550+
$index = 1;
551+
while ($value = $this->getRequestData("fraud_management_pending_filters_{$index}")) {
541552
$fraudFilters[] = $value;
553+
$index++;
542554
}
543555
if ($fraudFilters) {
544556
$from[Info::FRAUD_FILTERS] = $fraudFilters;
@@ -568,6 +580,7 @@ protected function _importPaymentInformation()
568580

569581
/**
570582
* Generate an "IPN" comment with additional explanation.
583+
*
571584
* Returns the generated comment or order status history object
572585
*
573586
* @param string $comment

dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
*/
66
namespace Magento\Paypal\Model;
77

8-
use Magento\Paypal\Model\IpnFactory;
8+
use Magento\Framework\Api\SearchCriteriaBuilder;
99
use Magento\Sales\Api\Data\OrderInterface;
10+
use Magento\Sales\Api\OrderRepositoryInterface;
1011
use Magento\Sales\Model\Order;
1112
use Magento\Sales\Model\Order\Creditmemo;
13+
use Magento\Sales\Model\Order\Invoice;
14+
use Magento\TestFramework\Helper\Bootstrap;
1215

1316
/**
1417
* @magentoAppArea frontend
@@ -22,7 +25,7 @@ class IpnTest extends \PHPUnit\Framework\TestCase
2225

2326
protected function setUp()
2427
{
25-
$this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
28+
$this->_objectManager = Bootstrap::getObjectManager();
2629
}
2730

2831
/**
@@ -158,6 +161,39 @@ public function testProcessIpnRequestRestRefund()
158161
$this->assertEmpty($order->getTotalOfflineRefunded());
159162
}
160163

164+
/**
165+
* Verifies canceling an order that was in payment review state by PayPal Express IPN message service.
166+
*
167+
* @magentoDataFixture Magento/Paypal/_files/order_express_with_invoice_payment_review.php
168+
* @magentoConfigFixture current_store payment/paypal_express/active 1
169+
* @magentoConfigFixture current_store paypal/general/merchant_country US
170+
*/
171+
public function testProcessIpnRequestWithFailedStatus()
172+
{
173+
$ipnData = require __DIR__ . '/../_files/ipn_failed.php';
174+
175+
/** @var IpnFactory $ipnFactory */
176+
$ipnFactory = $this->_objectManager->create(IpnFactory::class);
177+
$ipnModel = $ipnFactory->create(
178+
[
179+
'data' => $ipnData,
180+
'curlFactory' => $this->_createMockedHttpAdapter()
181+
]
182+
);
183+
184+
$ipnModel->processIpnRequest();
185+
186+
$order = $this->getOrder($ipnData['invoice']);
187+
$invoiceItems = $order->getInvoiceCollection()
188+
->getItems();
189+
/** @var Invoice $invoice */
190+
$invoice = array_pop($invoiceItems);
191+
$invoice->getState();
192+
193+
$this->assertEquals(Order::STATE_CANCELED, $order->getState());
194+
$this->assertEquals(Invoice::STATE_CANCELED, $invoice->getState());
195+
}
196+
161197
/**
162198
* Test processIpnRequest() currency check for paypal_express and paypal_standard payment methods
163199
*
@@ -224,4 +260,25 @@ protected function _createMockedHttpAdapter()
224260
$factory->expects($this->once())->method('create')->with()->will($this->returnValue($adapter));
225261
return $factory;
226262
}
263+
264+
/**
265+
* Get stored order.
266+
*
267+
* @param string $incrementId
268+
* @return OrderInterface
269+
*/
270+
private function getOrder(string $incrementId)
271+
{
272+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
273+
$searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class);
274+
$searchCriteria = $searchCriteriaBuilder->addFilter(OrderInterface::INCREMENT_ID, $incrementId)
275+
->create();
276+
277+
$orderRepository = $this->_objectManager->get(OrderRepositoryInterface::class);
278+
$orders = $orderRepository->getList($searchCriteria)
279+
->getItems();
280+
281+
/** @var OrderInterface $order */
282+
return array_pop($orders);
283+
}
227284
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
return [
9+
'invoice' => '100000002',
10+
'payment_status' => 'Failed',
11+
'receiver_email' => 'merchant_2012050718_biz@example.com',
12+
'parent_txn_id' => '84J11393WC835693U',
13+
'payer_status' => 'verified',
14+
'payment_type' => 'instant',
15+
'txn_id' => '1P566839F9694230H',
16+
'txn_type' => 'cart'
17+
];
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
use Magento\Framework\DB\Transaction;
9+
use Magento\Paypal\Model\Config;
10+
use Magento\Sales\Api\InvoiceManagementInterface;
11+
use Magento\Sales\Api\OrderRepositoryInterface;
12+
use Magento\Sales\Model\Order;
13+
use Magento\Sales\Model\Order\Item;
14+
use Magento\Sales\Model\Order\Address;
15+
use Magento\Sales\Model\Order\Payment;
16+
use Magento\Sales\Model\Service\InvoiceService;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
19+
require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php';
20+
21+
$objectManager = Bootstrap::getObjectManager();
22+
23+
$addressData = include __DIR__ . '/address_data.php';
24+
$billingAddress = $objectManager->create(
25+
Address::class,
26+
['data' => $addressData]
27+
);
28+
$billingAddress->setAddressType('billing');
29+
$shippingAddress = clone $billingAddress;
30+
$shippingAddress->setId(null)->setAddressType('shipping');
31+
32+
$payment = $objectManager->create(Payment::class);
33+
$payment->setMethod(Config::METHOD_WPP_EXPRESS);
34+
35+
/** @var Item $orderItem */
36+
$orderItem = $objectManager->create(Item::class);
37+
$orderItem->setProductId($product->getId())->setQtyOrdered(1);
38+
$orderItem->setBasePrice($product->getPrice());
39+
$orderItem->setPrice($product->getPrice());
40+
$orderItem->setRowTotal($product->getPrice());
41+
$orderItem->setRowTotalInclTax($product->getPrice());
42+
$orderItem->setBaseRowTotal($product->getPrice());
43+
$orderItem->setBaseRowTotalInclTax($product->getPrice());
44+
$orderItem->setBaseRowInvoiced($product->getPrice());
45+
$orderItem->setProductType('simple');
46+
47+
$itemsAmount = $product->getPrice();
48+
$shippingAmount = 20;
49+
$totalAmount = $itemsAmount + $shippingAmount;
50+
51+
/** @var Order $order */
52+
$order = $objectManager->create(Order::class);
53+
$order->setCustomerEmail('co@co.co')
54+
->setIncrementId('100000002')
55+
->addItem($orderItem)
56+
->setSubtotal($itemsAmount)
57+
->setBaseSubtotal($itemsAmount)
58+
->setBaseGrandTotal($totalAmount)
59+
->setGrandTotal($totalAmount)
60+
->setBaseCurrencyCode('USD')
61+
->setCustomerIsGuest(true)
62+
->setStoreId(1)
63+
->setEmailSent(true)
64+
->setState(Order::STATE_PAYMENT_REVIEW)
65+
->setBillingAddress($billingAddress)
66+
->setShippingAddress($shippingAddress)
67+
->setBaseTotalPaid($totalAmount)
68+
->setTotalPaid($totalAmount)
69+
->setData('base_to_global_rate', 1)
70+
->setData('base_to_order_rate', 1)
71+
->setData('shipping_amount', $shippingAmount)
72+
->setData('base_shipping_amount', $shippingAmount)
73+
->setPayment($payment);
74+
75+
/** @var OrderRepositoryInterface $orderRepository */
76+
$orderRepository = $objectManager->get(OrderRepositoryInterface::class);
77+
$orderRepository->save($order);
78+
79+
/** @var InvoiceService $invoiceService */
80+
$invoiceService = $objectManager->create(InvoiceManagementInterface::class);
81+
82+
/** @var Transaction $transaction */
83+
$transaction = $objectManager->create(Transaction::class);
84+
85+
$invoice = $invoiceService->prepareInvoice($order, [$orderItem->getId() => 1]);
86+
$invoice->register();
87+
88+
$transaction->addObject($invoice)->addObject($order)->save();

0 commit comments

Comments
 (0)