Skip to content

Commit 757ccf1

Browse files
committed
MAGETWO-68833: [Magento Cloud] - Paypal shows error in checkout but order still goes through but all subsequent orders fail afterwards
- Refactored fields filter - Covered request with line items by integration test
1 parent 17f8406 commit 757ccf1

File tree

4 files changed

+258
-20
lines changed

4 files changed

+258
-20
lines changed

app/code/Magento/Paypal/Model/Api/AbstractApi.php

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ abstract class AbstractApi extends \Magento\Framework\DataObject
5353
* @var array
5454
*/
5555
protected $_lineItemExportItemsFilters = [
56-
'name' => 'convertToString'
56+
'name' => 'strval'
5757
];
5858

5959
/**
@@ -356,17 +356,6 @@ public function getFraudManagementFiltersEnabled()
356356
return 1;
357357
}
358358

359-
/**
360-
* Converts provided value to string.
361-
*
362-
* @param string|int|\Magento\Framework\Phrase $value
363-
* @return string
364-
*/
365-
public function convertToString($value)
366-
{
367-
return strval($value);
368-
}
369-
370359
/**
371360
* Export $this public data to private request array
372361
*
@@ -453,14 +442,7 @@ protected function _exportLineItems(array &$request, $i = 0)
453442
foreach ($this->_lineItemExportItemsFormat as $publicKey => $privateFormat) {
454443
$result = true;
455444
$value = $item->getDataUsingMethod($publicKey);
456-
if (isset($this->_lineItemExportItemsFilters[$publicKey])) {
457-
$callback = $this->_lineItemExportItemsFilters[$publicKey];
458-
$value = call_user_func([$this, $callback], $value);
459-
}
460-
if (is_float($value)) {
461-
$value = $this->formatPrice($value);
462-
}
463-
$request[sprintf($privateFormat, $i)] = $value;
445+
$request[sprintf($privateFormat, $i)] = $this->formatValue($value, $publicKey);
464446
}
465447
$i++;
466448
}
@@ -648,4 +630,25 @@ public function getDebugReplacePrivateDataKeys()
648630
{
649631
return $this->_debugReplacePrivateDataKeys;
650632
}
633+
634+
/**
635+
* Formats value according to configured filters or converts to 0.00 format if value is float.
636+
*
637+
* @param string|int|float|\Magento\Framework\Phrase $value
638+
* @param $publicKey
639+
* @return string
640+
*/
641+
private function formatValue($value, $publicKey)
642+
{
643+
if (!empty($this->_lineItemExportItemsFilters[$publicKey])) {
644+
$callback = $this->_lineItemExportItemsFilters[$publicKey];
645+
$value = method_exists($this, $callback) ? $this->{$callback}($value) : $callback($value);
646+
}
647+
648+
if (is_float($value)) {
649+
$value = $this->formatPrice($value);
650+
}
651+
652+
return $value;
653+
}
651654
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Paypal\Model\Api;
7+
8+
use Magento\Framework\Api\SearchCriteriaBuilder;
9+
use Magento\Framework\HTTP\Adapter\Curl;
10+
use Magento\Framework\HTTP\Adapter\CurlFactory;
11+
use Magento\Framework\ObjectManagerInterface;
12+
use Magento\Paypal\Model\CartFactory;
13+
use Magento\Quote\Model\Quote;
14+
use Magento\Quote\Model\QuoteRepository;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
use PHPUnit_Framework_MockObject_MockObject as MockObject;
17+
use Magento\Paypal\Model\Config;
18+
19+
class PayflowNvpTest extends \PHPUnit_Framework_TestCase
20+
{
21+
/**
22+
* @var PayflowNvp
23+
*/
24+
private $nvpApi;
25+
26+
/**
27+
* @var ObjectManagerInterface
28+
*/
29+
private $objectManager;
30+
31+
/**
32+
* @var Curl|MockObject
33+
*/
34+
private $httpClient;
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
protected function setUp()
40+
{
41+
$this->objectManager = Bootstrap::getObjectManager();
42+
43+
/** @var CurlFactory|MockObject $httpFactory */
44+
$httpFactory = $this->getMockBuilder(CurlFactory::class)
45+
->disableOriginalConstructor()
46+
->getMock();
47+
48+
$this->httpClient = $this->getMockBuilder(Curl::class)
49+
->disableOriginalConstructor()
50+
->getMock();
51+
$httpFactory->method('create')
52+
->willReturn($this->httpClient);
53+
54+
$this->nvpApi = $this->objectManager->create(PayflowNvp::class, [
55+
'curlFactory' => $httpFactory
56+
]);
57+
58+
/** @var Config $config */
59+
$config = $this->objectManager->get(Config::class);
60+
$config->setMethodCode(Config::METHOD_WPP_PE_EXPRESS);
61+
$this->nvpApi->setConfigObject($config);
62+
}
63+
64+
/**
65+
* Checks a case when items and discount are present in the request.
66+
*
67+
* @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php
68+
*/
69+
public function testRequestLineItems()
70+
{
71+
$quote = $this->getQuote('100000015');
72+
/** @var CartFactory $cartFactory */
73+
$cartFactory = $this->objectManager->get(CartFactory::class);
74+
$cart = $cartFactory->create(['salesModel' => $quote]);
75+
76+
$request = 'TENDER=P&AMT=52.14&FREIGHTAMT=0.00&TAXAMT=0.00&'
77+
. 'L_NAME0=Simple 1&L_QTY0=1&L_COST0=7.69&'
78+
. 'L_NAME1=Simple 2&L_QTY1=2&L_COST1=9.69&'
79+
. 'L_NAME2=Simple 3&L_QTY2=3&L_COST2=11.69&'
80+
. 'L_NAME3=Discount&L_QTY3=1&L_COST3=-10.00&'
81+
. 'TRXTYPE=A&ACTION=S&BUTTONSOURCE=Magento_Cart_Community';
82+
83+
$this->httpClient->method('write')
84+
->with(
85+
'POST',
86+
'https://payflowpro.paypal.com/transaction',
87+
'1.1',
88+
['PAYPAL-NVP: Y'],
89+
self::equalTo($request)
90+
);
91+
92+
$this->httpClient->method('read')
93+
->willReturn("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nRESULT=0&RESPMSG=Approved");
94+
95+
$this->nvpApi->setAmount($quote->getBaseGrandTotal());
96+
$this->nvpApi->setPaypalCart($cart);
97+
$this->nvpApi->setQuote($quote);
98+
$this->nvpApi->setIsLineItemsEnabled(true);
99+
$this->nvpApi->callSetExpressCheckout();
100+
}
101+
102+
/**
103+
* Gets quote by reserved order id.
104+
*
105+
* @param string $reservedOrderId
106+
* @return Quote
107+
*/
108+
private function getQuote($reservedOrderId)
109+
{
110+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
111+
$searchCriteriaBuilder = $this->objectManager->create(SearchCriteriaBuilder::class);
112+
$searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId)
113+
->create();
114+
/** @var QuoteRepository $quoteRepository */
115+
$quoteRepository = $this->objectManager->get(QuoteRepository::class);
116+
$items = $quoteRepository->getList($searchCriteria)
117+
->getItems();
118+
return array_pop($items);
119+
}
120+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
use Magento\Framework\ObjectManagerInterface;
7+
use Magento\SalesRule\Api\CouponRepositoryInterface;
8+
use Magento\SalesRule\Api\Data\CouponInterface;
9+
use Magento\SalesRule\Api\Data\RuleInterface;
10+
use Magento\SalesRule\Api\RuleRepositoryInterface;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
13+
/** @var ObjectManagerInterface $objectManager */
14+
$objectManager = Bootstrap::getObjectManager();
15+
16+
/** @var RuleInterface $rule */
17+
$rule = $objectManager->create(RuleInterface::class);
18+
$rule->setName('10$ discount')
19+
->setIsAdvanced(true)
20+
->setStopRulesProcessing(false)
21+
->setDiscountQty(10)
22+
->setCustomerGroupIds([0])
23+
->setWebsiteIds([1])
24+
->setCouponType(RuleInterface::COUPON_TYPE_SPECIFIC_COUPON)
25+
->setSimpleAction(RuleInterface::DISCOUNT_ACTION_FIXED_AMOUNT_FOR_CART)
26+
->setDiscountAmount(10)
27+
->setIsActive(true);
28+
29+
/** @var RuleRepositoryInterface $ruleRepository */
30+
$ruleRepository = $objectManager->get(RuleRepositoryInterface::class);
31+
$rule = $ruleRepository->save($rule);
32+
33+
/** @var CouponInterface $coupon */
34+
$coupon = $objectManager->create(CouponInterface::class);
35+
$coupon->setCode('10_discount')
36+
->setRuleId($rule->getRuleId());
37+
38+
/** @var CouponRepositoryInterface $couponRepository */
39+
$couponRepository = $objectManager->get(CouponRepositoryInterface::class);
40+
$coupon = $couponRepository->save($coupon);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
use Magento\Catalog\Api\Data\ProductInterface;
7+
use Magento\Catalog\Api\ProductRepositoryInterface;
8+
use Magento\Framework\ObjectManagerInterface;
9+
use Magento\Quote\Model\Quote;
10+
use Magento\Quote\Model\Quote\Address;
11+
use Magento\Quote\Model\QuoteRepository;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
14+
require 'fixed_discount.php';
15+
16+
/** @var ObjectManagerInterface $objectManager */
17+
$objectManager = Bootstrap::getObjectManager();
18+
19+
$addressData = [
20+
'firstname' => 'John',
21+
'lastname' => 'Doe',
22+
'company' => '',
23+
'email' => '[email protected]',
24+
'street' => [
25+
0 => 'test1',
26+
],
27+
'city' => 'Test',
28+
'region_id' => '1',
29+
'region' => '',
30+
'postcode' => '9001',
31+
'country_id' => 'US',
32+
'telephone' => '11111111',
33+
];
34+
/** @var Address $billingAddress */
35+
$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]);
36+
$billingAddress->setAddressType('billing');
37+
38+
$shippingAddress = clone $billingAddress;
39+
$shippingAddress->setAddressType('shipping')
40+
->setId(null);
41+
42+
/** @var Quote $quote */
43+
$quote = $objectManager->create(Quote::class);
44+
$quote->setCustomerIsGuest(true)
45+
->setReservedOrderId('100000015')
46+
->setBillingAddress($billingAddress)
47+
->setShippingAddress($shippingAddress);
48+
49+
/** @var ProductRepositoryInterface $productRepository */
50+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
51+
for ($i = 1; $i <= 3; $i++) {
52+
/** @var ProductInterface $product */
53+
$product = $objectManager->create(ProductInterface::class);
54+
$product->setTypeId('simple')
55+
->setName('Simple ' . $i)
56+
->setSku('simple' . $i)
57+
->setAttributeSetId(4)
58+
->setStockData(
59+
[
60+
'qty' => 10,
61+
'is_in_stock' => 10,
62+
]
63+
)
64+
->setPrice(5.69 + $i * 2)
65+
->setWeight(1);
66+
$item = $productRepository->save($product);
67+
$quote->addProduct($item, $i);
68+
}
69+
70+
$quote->setCouponCode($coupon->getCode());
71+
$quote->collectTotals();
72+
73+
/** @var QuoteRepository $quoteRepository */
74+
$quoteRepository = $objectManager->get(QuoteRepository::class);
75+
$quoteRepository->save($quote);

0 commit comments

Comments
 (0)