diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index d755cc71cce2b932f471e5148639b6e2b355db58..c4261e9581b82c92fec3505aae4180a1fe30011f 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -119,6 +119,11 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra */ protected $transactionRepository; + /** + * @var \Psr\Log\LoggerInterface + */ + private $psrLogger; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -761,7 +766,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra { try { $transactionId = $this->getResponse()->getXTransId(); - $data = $payment->getMethodInstance()->getTransactionDetails($transactionId); + $data = $this->transactionService->getTransactionDetails($this, $transactionId); $transactionStatus = (string)$data->transaction->transactionStatus; $fdsFilterAction = (string)$data->transaction->FDSFilterAction; @@ -779,6 +784,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra $payment->getOrder()->addStatusHistoryComment($message); } } catch (\Exception $e) { + $this->getPsrLogger()->critical($e); //this request is optional } return $this; @@ -805,7 +811,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra $order->registerCancellation($message)->save(); } catch (\Exception $e) { //quiet decline - $this->logger->critical($e); + $this->getPsrLogger()->critical($e); } } @@ -973,4 +979,18 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra return $response; } + + /** + * @return \Psr\Log\LoggerInterface + * + * @deprecated + */ + private function getPsrLogger() + { + if (null === $this->psrLogger) { + $this->psrLogger = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Psr\Log\LoggerInterface::class); + } + return $this->psrLogger; + } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js index f88b11bf87a43e7b2feb4ec95fb3eafb6f69d056..2d142417ac099b023d2e715cbac026cfdada14e9 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js @@ -6,48 +6,31 @@ define( [ 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/url-builder', - 'mage/storage', - 'Magento_Checkout/js/model/error-processor', 'Magento_Customer/js/model/customer', - 'Magento_Checkout/js/model/full-screen-loader' + 'Magento_Checkout/js/model/place-order' ], - function (quote, urlBuilder, storage, errorProcessor, customer, fullScreenLoader) { + function (quote, urlBuilder, customer, placeOrderService) { 'use strict'; return function (paymentData, messageContainer) { - var serviceUrl, - payload; + var serviceUrl, payload; - /** Checkout for guest and registered customer. */ - if (!customer.isLoggedIn()) { + payload = { + cartId: quote.getQuoteId(), + billingAddress: quote.billingAddress(), + paymentMethod: paymentData + }; + + if (customer.isLoggedIn()) { + serviceUrl = urlBuilder.createUrl('/carts/mine/payment-information', {}); + } else { serviceUrl = urlBuilder.createUrl('/guest-carts/:quoteId/payment-information', { quoteId: quote.getQuoteId() }); - payload = { - cartId: quote.getQuoteId(), - email: quote.guestEmail, - paymentMethod: paymentData, - billingAddress: quote.billingAddress() - }; - } else { - serviceUrl = urlBuilder.createUrl('/carts/mine/payment-information', {}); - payload = { - cartId: quote.getQuoteId(), - paymentMethod: paymentData, - billingAddress: quote.billingAddress() - }; + payload.email = quote.guestEmail; } - fullScreenLoader.startLoader(); - - return storage.post( - serviceUrl, JSON.stringify(payload) - ).fail( - function (response) { - errorProcessor.process(response, messageContainer); - fullScreenLoader.stopLoader(); - } - ); + return placeOrderService(serviceUrl, payload, messageContainer); }; } ); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js index 8b079d1fd5d15fecb0a8a63ec7e1c7d9e27d29a7..ab46c78c6f6e74f62a8c798c4c2b039b2eaf4876 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js @@ -4,18 +4,19 @@ */ /*jshint browser:true jquery:true*/ /*global alert*/ -define([], function() { +define([], function () { /** - * @param addressData + * @param {Object} addressData * Returns new address object */ return function (addressData) { var identifier = Date.now(); + return { email: addressData.email, countryId: (addressData.country_id) ? addressData.country_id : window.checkoutConfig.defaultCountryId, - regionId: (addressData.region && addressData.region.region_id) - ? addressData.region.region_id + regionId: (addressData.region && addressData.region.region_id) ? + addressData.region.region_id : window.checkoutConfig.defaultRegionId, regionCode: (addressData.region) ? addressData.region.region_code : null, region: (addressData.region) ? addressData.region.region : null, @@ -33,25 +34,26 @@ define([], function() { suffix: addressData.suffix, vatId: addressData.vat_id, saveInAddressBook: addressData.save_in_address_book, - isDefaultShipping: function() { + customAttributes: addressData.custom_attributes, + isDefaultShipping: function () { return addressData.default_shipping; }, - isDefaultBilling: function() { + isDefaultBilling: function () { return addressData.default_billing; }, - getType: function() { + getType: function () { return 'new-customer-address'; }, - getKey: function() { + getKey: function () { return this.getType(); }, - getCacheKey: function() { + getCacheKey: function () { return this.getType() + identifier; }, - isEditable: function() { + isEditable: function () { return true; }, - canUseForBilling: function() { + canUseForBilling: function () { return true; } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js new file mode 100644 index 0000000000000000000000000000000000000000..68548cb1b28d3e2a4abe66c8be13c9dd48b73ec2 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define( + [ + 'mage/storage', + 'Magento_Checkout/js/model/error-processor', + 'Magento_Checkout/js/model/full-screen-loader' + ], + function (storage, errorProcessor, fullScreenLoader) { + 'use strict'; + + return function (serviceUrl, payload, messageContainer) { + fullScreenLoader.startLoader(); + + return storage.post( + serviceUrl, JSON.stringify(payload) + ).fail( + function (response) { + errorProcessor.process(response, messageContainer); + fullScreenLoader.stopLoader(); + } + ); + }; + } +); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js index eb950d1e6f9a3d7d484c2379026a1cfd3a03d809..6f86ecedd1af3f5f85909c338ccd00e6cf390dc2 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js @@ -44,7 +44,7 @@ define( return totals; }, setTotals: function(totalsData) { - if (_.isObject(totalsData.extension_attributes)) { + if (_.isObject(totalsData) && _.isObject(totalsData.extension_attributes)) { _.each(totalsData.extension_attributes, function(element, index) { totalsData[index] = element; }); diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index 54eeaf09f1ff76ef87522cccb5f2cc3d77107281..0d7638a37196a9062f05cc34e7ef9b2d983176c6 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -47,6 +47,11 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress */ protected $indexerRegistry; + /** + * @var \Magento\Customer\Model\Address\CustomAttributeListInterface + */ + private $attributeList; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -347,4 +352,27 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexRow($this->getCustomerId()); } + + /** + * {@inheritdoc} + */ + protected function getCustomAttributesCodes() + { + return array_keys($this->getAttributeList()->getAttributes()); + } + + /** + * Get new AttributeList dependency for application code. + * @return \Magento\Customer\Model\Address\CustomAttributeListInterface + * @deprecated + */ + private function getAttributeList() + { + if (!$this->attributeList) { + $this->attributeList = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Customer\Model\Address\CustomAttributeListInterface::class + ); + } + return $this->attributeList; + } } diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 6855eb242e6e5f1e9b476a175dadc841d2b9fbf6..1851f858b992f2acfdd8285aeaca897682b24792 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -263,7 +263,7 @@ class AbstractAddress extends AbstractExtensibleModel implements AddressModelInt { if (is_array($key)) { $key = $this->_implodeArrayField($key); - } elseif (is_array($value) && $this->isAddressMultilineAttribute($key)) { + } elseif (is_array($value) && !empty($value) && $this->isAddressMultilineAttribute($key)) { $value = $this->_implodeArrayValues($value); } return parent::setData($key, $value); diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributeList.php b/app/code/Magento/Customer/Model/Address/CustomAttributeList.php new file mode 100644 index 0000000000000000000000000000000000000000..f83b37c2a814debcc5d6b0c1ccd6a429cb3a755a --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomAttributeList.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Address; + +class CustomAttributeList implements CustomAttributeListInterface +{ + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return []; + } +} diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php b/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c04cfe16f49183e0e27ab8362068b75a83951d3c --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Address; + +interface CustomAttributeListInterface +{ + /** + * Retrieve list of customer addresses custom attributes + * + * @return array + */ + public function getAttributes(); +} diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index 647ec87efca25a1e583df18d3d469f586996b2c2..1e5f0ce0677ecf699b12b540bb9805d8129d053e 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -49,6 +49,8 @@ type="Magento\Customer\Model\EmailNotification" /> <preference for="Magento\Customer\Api\CustomerNameGenerationInterface" type="Magento\Customer\Helper\View" /> + <preference for="Magento\Customer\Model\Address\CustomAttributeListInterface" + type="Magento\Customer\Model\Address\CustomAttributeList" /> <type name="Magento\Customer\Model\Session"> <arguments> <argument name="configShare" xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument> diff --git a/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js b/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js index e81016145c705e9d99d86265fb6f0dfd284f9d19..30fbef98fd39ab15db1c7971e3e86ad82e367253 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js +++ b/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js @@ -32,6 +32,7 @@ define([], function() { vatId: addressData.vat_id, sameAsBilling: addressData.same_as_billing, saveInAddressBook: addressData.save_in_address_book, + customAttributes: addressData.custom_attributes, isDefaultShipping: function() { return addressData.default_shipping; }, diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index b9725a98209599aac6fe187ded298d03a8070e97..8a626dd2d42b8cdd5af810d3dd64ef58d7055834 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -1123,9 +1123,8 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin throw new \Magento\Framework\Exception\LocalizedException($responseError); } $this->debugErrors($this->_errors); - - return false; } + $result->append($this->getErrorMessage()); } return $result; diff --git a/app/code/Magento/Fedex/Model/Carrier.php b/app/code/Magento/Fedex/Model/Carrier.php index c03d89e480c355c2e6abe3f1d9d1585e66a793a4..4cd95cb5b4ba9d3c433d311440b509ee93c6dd3e 100644 --- a/app/code/Magento/Fedex/Model/Carrier.php +++ b/app/code/Magento/Fedex/Model/Carrier.php @@ -501,7 +501,10 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C // make general request for all methods $response = $this->_doRatesRequest(self::RATE_REQUEST_GENERAL); $preparedGeneral = $this->_prepareRateResponse($response); - if (!$preparedGeneral->getError() || $this->_result->getError() && $preparedGeneral->getError()) { + if (!$preparedGeneral->getError() + || $this->_result->getError() && $preparedGeneral->getError() + || empty($this->_result->getAllRates()) + ) { $this->_result->append($preparedGeneral); } diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php index a824adbd2bf82ed733841813ba42d72622bfdf39..04e149cae270e1ff5077e55ce0283f9a51066e82 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php @@ -62,15 +62,21 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress */ public function execute() { - try { - if ($this->isValidationRequired() && - !$this->agreementsValidator->isValid(array_keys($this->getRequest()->getPost('agreement', []))) - ) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Please agree to all the terms and conditions before placing the order.') - ); - } + if ($this->isValidationRequired() && + !$this->agreementsValidator->isValid(array_keys($this->getRequest()->getPost('agreement', []))) + ) { + $e = new \Magento\Framework\Exception\LocalizedException( + __('Please agree to all the terms and conditions before placing the order.') + ); + $this->messageManager->addExceptionMessage( + $e, + $e->getMessage() + ); + $this->_redirect('*/*/review'); + return; + } + try { $this->_initCheckout(); $this->_checkout->place($this->_initToken()); @@ -108,12 +114,6 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress return; } catch (ApiProcessableException $e) { $this->_processPaypalApiError($e); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addExceptionMessage( - $e, - $e->getMessage() - ); - $this->_redirect('*/*/review'); } catch (\Exception $e) { $this->messageManager->addExceptionMessage( $e, @@ -141,6 +141,9 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress case ApiProcessableException::API_DO_EXPRESS_CHECKOUT_FAIL: $this->_redirectSameToken(); break; + case ApiProcessableException::API_ADDRESS_MATCH_FAIL: + $this->redirectToOrderReviewPageAndShowError($exception->getUserMessage()); + break; case ApiProcessableException::API_UNABLE_TRANSACTION_COMPLETE: if ($this->_config->getPaymentAction() == \Magento\Payment\Model\Method\AbstractMethod::ACTION_ORDER) { $paypalTransactionData = $this->_getCheckoutSession()->getPaypalTransactionData(); @@ -182,6 +185,18 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress $this->_redirect('checkout/cart'); } + /** + * Redirect customer to the paypal order review page and show error message + * + * @param string $errorMessage + * @return void + */ + private function redirectToOrderReviewPageAndShowError($errorMessage) + { + $this->messageManager->addErrorMessage($errorMessage); + $this->_redirect('*/*/review'); + } + /** * Return true if agreements validation required * diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/Start.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/Start.php index 20c9b5f7fb08fbcd78fc46cfa7c2e8ebce3436a1..aef1f4aa87920d3dc1a801ff5ee47b2c33ff84c5 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/Start.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/Start.php @@ -21,7 +21,6 @@ class Start extends GetToken public function execute() { try { - $token = $this->getToken(); if ($token === null) { diff --git a/app/code/Magento/Paypal/Controller/Express/GetToken.php b/app/code/Magento/Paypal/Controller/Express/GetToken.php index c96cdb420bb3aca6bacfe83a9eb281a574e37c39..a7c00bf1f38322c811422f3e4b26f84934ed8bfd 100644 --- a/app/code/Magento/Paypal/Controller/Express/GetToken.php +++ b/app/code/Magento/Paypal/Controller/Express/GetToken.php @@ -53,8 +53,9 @@ class GetToken extends AbstractExpress if ($token === null) { $token = false; } + $url = $this->_checkout->getRedirectUrl(); $this->_initToken($token); - $controllerResult->setData(['token' => $token]); + $controllerResult->setData(['url' => $url]); } catch (LocalizedException $exception) { $this->messageManager->addExceptionMessage( $exception, diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php index 521887b6c3c524e4f82fb348bfcc47d63f2db99e..d1cc47a06ff8ff89d4f1b0d95748184ad0244e4d 100644 --- a/app/code/Magento/Paypal/Model/Api/Nvp.php +++ b/app/code/Magento/Paypal/Model/Api/Nvp.php @@ -1288,9 +1288,10 @@ class Nvp extends \Magento\Paypal\Model\Api\AbstractApi $exceptionPhrase = __('PayPal gateway has rejected request. %1', $errorMessages); /** @var \Magento\Framework\Exception\LocalizedException $exception */ - $exception = count($errors) == 1 && $this->_isProcessableError($errors[0]['code']) + $firstError = $errors[0]['code']; + $exception = $this->_isProcessableError($firstError) ? $this->_processableExceptionFactory->create( - ['phrase' => $exceptionPhrase, 'code' => $errors[0]['code']] + ['phrase' => $exceptionPhrase, 'code' => $firstError] ) : $this->_frameworkExceptionFactory->create( ['phrase' => $exceptionPhrase] diff --git a/app/code/Magento/Paypal/Model/Api/ProcessableException.php b/app/code/Magento/Paypal/Model/Api/ProcessableException.php index a44e9ae5f3e753ef8c8a2e99a1bf768de8f16bfb..2a7ce0f104203c1a2803e1d77314f3c04cefbf0e 100644 --- a/app/code/Magento/Paypal/Model/Api/ProcessableException.php +++ b/app/code/Magento/Paypal/Model/Api/ProcessableException.php @@ -25,6 +25,7 @@ class ProcessableException extends LocalizedException const API_COUNTRY_FILTER_DECLINE = 10537; const API_MAXIMUM_AMOUNT_FILTER_DECLINE = 10538; const API_OTHER_FILTER_DECLINE = 10539; + const API_ADDRESS_MATCH_FAIL = 10736; /**#@-*/ /** @@ -61,8 +62,13 @@ class ProcessableException extends LocalizedException 'I\'m sorry - but we are not able to complete your transaction. Please contact us so we can assist you.' ); break; + case self::API_ADDRESS_MATCH_FAIL: + $message = __( + 'A match of the Shipping Address City, State, and Postal Code failed.' + ); + break; default: - $message = __($this->getMessage()); + $message = __('We can\'t place the order.'); break; } return $message; diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index 2464ef48498ce967dc6b074e695f4b685bfc3bd0..a10a2654bf529b63a70c07247bba9b9c154368d0 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -266,6 +266,7 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod ApiProcessableException::API_COUNTRY_FILTER_DECLINE, ApiProcessableException::API_MAXIMUM_AMOUNT_FILTER_DECLINE, ApiProcessableException::API_OTHER_FILTER_DECLINE, + ApiProcessableException::API_ADDRESS_MATCH_FAIL, ] ); } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Api/ProcessableExceptionTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Api/ProcessableExceptionTest.php index 63c6d364e82f43bddcc14fe7595560e494aef7fc..c90431dc4dc5979136d5f274517af4efe7c67c21 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Api/ProcessableExceptionTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Api/ProcessableExceptionTest.php @@ -5,19 +5,16 @@ */ namespace Magento\Paypal\Test\Unit\Model\Api; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Paypal\Model\Api\ProcessableException; class ProcessableExceptionTest extends \PHPUnit_Framework_TestCase { - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $objectManager; + const UNKNOWN_CODE = 10411; /** - * @var \Magento\Paypal\Model\Api\ProcessableException + * @var ProcessableException */ - protected $model; + private $model; /** * @param int $code @@ -27,8 +24,7 @@ class ProcessableExceptionTest extends \PHPUnit_Framework_TestCase */ public function testGetUserMessage($code, $msg) { - $this->objectManager = new ObjectManager($this); - $this->model = new \Magento\Paypal\Model\Api\ProcessableException(__($msg), null, $code); + $this->model = new ProcessableException(__($msg), null, $code); $this->assertEquals($msg, $this->model->getUserMessage()); } @@ -39,28 +35,35 @@ class ProcessableExceptionTest extends \PHPUnit_Framework_TestCase { return [ [ - 10001, + ProcessableException::API_INTERNAL_ERROR, "I'm sorry - but we were not able to process your payment. " . "Please try another payment method or contact us so we can assist you.", ], [ - 10417, + ProcessableException::API_UNABLE_PROCESS_PAYMENT_ERROR_CODE, "I'm sorry - but we were not able to process your payment. " . "Please try another payment method or contact us so we can assist you." ], [ - 10537, + ProcessableException::API_COUNTRY_FILTER_DECLINE, "I'm sorry - but we are not able to complete your transaction. Please contact us so we can assist you." ], [ - 10538, + ProcessableException::API_MAXIMUM_AMOUNT_FILTER_DECLINE, "I'm sorry - but we are not able to complete your transaction. Please contact us so we can assist you." ], [ - 10539, + ProcessableException::API_OTHER_FILTER_DECLINE, "I'm sorry - but we are not able to complete your transaction. Please contact us so we can assist you." ], - [10411, "something went wrong"] + [ + ProcessableException::API_ADDRESS_MATCH_FAIL, + 'A match of the Shipping Address City, State, and Postal Code failed.' + ], + [ + self::UNKNOWN_CODE, + "We can't place the order." + ] ]; } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php index 288b01bd05d399f3599158e686295044d250d767..75f4c14d8517e23b08dc59f46850ae63df5d4830 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php @@ -22,6 +22,7 @@ class ExpressTest extends \PHPUnit_Framework_TestCase ApiProcessableException::API_COUNTRY_FILTER_DECLINE, ApiProcessableException::API_MAXIMUM_AMOUNT_FILTER_DECLINE, ApiProcessableException::API_OTHER_FILTER_DECLINE, + ApiProcessableException::API_ADDRESS_MATCH_FAIL ]; /** diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js index e2f70141de02218a46bf0222af200e503ac35170..d70de53581276d34886263558deb455f03a55094 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js @@ -42,8 +42,8 @@ define( } ).done( function (response) { - if (response && response.token) { - paypalExpressCheckout.checkout.startFlow(response.token); + if (response && response.url) { + paypalExpressCheckout.checkout.startFlow(response.url); return; } diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js index 9d8ba97f26775ae95714cf8f15a2b892e6150fba..b95c0a7839a6dca8c21040c518192049e16611c5 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js @@ -50,6 +50,7 @@ define( */ placePendingPaymentOrder: function () { if (this.placeOrder()) { + fullScreenLoader.startLoader(); this.isInAction(true); // capture all click events document.addEventListener('click', iframe.stopEventPropagation, true); @@ -61,6 +62,7 @@ define( return this._super() .fail( function () { + fullScreenLoader.stopLoader(); self.isInAction(false); document.removeEventListener('click', iframe.stopEventPropagation, true); } @@ -71,7 +73,15 @@ define( * After place order callback */ afterPlaceOrder: function () { + if (this.iframeIsLoaded) { + document.getElementById(this.getCode() + '-iframe') + .contentWindow.location.reload(); + } + this.paymentReady(true); + this.iframeIsLoaded = true; + this.isPlaceOrderActionAllowed(true); + fullScreenLoader.stopLoader(); }, /** diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js index d93e0ea702fca98fcd447cc5786a830fa0f8df52..7538d3a02aa9b76a769845f5f32d471d110a7983 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js @@ -52,8 +52,8 @@ define( } ).done( function (response) { - if (response && response.token) { - paypalExpressCheckout.checkout.startFlow(response.token); + if (response && response.url) { + paypalExpressCheckout.checkout.startFlow(response.url); return; } diff --git a/app/code/Magento/Quote/Model/Quote/Item/Compare.php b/app/code/Magento/Quote/Model/Quote/Item/Compare.php index f61dc25660c9d1dd68b132aaceedc0fd7356315e..10bb712025462fc11f99c2f55206c6cf10f8b854 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Compare.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Compare.php @@ -23,6 +23,9 @@ class Compare if (is_string($value) && is_array(@unserialize($value))) { $value = @unserialize($value); unset($value['qty'], $value['uenc']); + $value = array_filter($value, function ($optionValue) { + return !empty($optionValue); + }); } return $value; } diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index d8aedf4a8b986a45e237e460e127ba4d5060a8a0..f69e299b4542e7669d9bc23df733d9bd8b63b242 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -21,6 +21,8 @@ use Magento\Sales\Api\Data\OrderInterfaceFactory as OrderFactory; use Magento\Sales\Api\OrderManagementInterface as OrderManagement; use Magento\Store\Model\StoreManagerInterface; use Magento\Quote\Model\Quote\Address; +use Magento\Framework\App\ObjectManager; +use Magento\Quote\Model\QuoteIdMaskFactory; /** * Class QuoteManagement @@ -130,6 +132,11 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface */ protected $quoteFactory; + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + /** * @param EventManager $eventManager * @param QuoteValidator $quoteValidator @@ -262,6 +269,12 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface $quote->setCustomer($customer); $quote->setCustomerIsGuest(0); + $quoteIdMaskFactory = $this->getQuoteIdMaskFactory(); + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + $quoteIdMask = $quoteIdMaskFactory->create()->load($cartId, 'quote_id'); + if ($quoteIdMask->getId()) { + $quoteIdMask->delete(); + } $this->quoteRepository->save($quote); return true; @@ -547,4 +560,16 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface $shipping->setIsDefaultBilling(true); } } + + /** + * @return QuoteIdMaskFactory + * @deprecated + */ + private function getQuoteIdMaskFactory() + { + if (!$this->quoteIdMaskFactory) { + $this->quoteIdMaskFactory = ObjectManager::getInstance()->get(QuoteIdMaskFactory::class); + } + return $this->quoteIdMaskFactory; + } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php index a22d10aee9318aed5d07fa70768e8ecfa90ca940..07f9987a902522b5701c6fdab888274f664d2073 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php @@ -187,4 +187,31 @@ class CompareTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue([])); $this->assertFalse($this->helper->compare($this->itemMock, $this->comparedMock)); } + + /** + * Verify that compare ignores empty options. + */ + public function testCompareWithEmptyValues() + { + $this->itemMock->expects($this->any()) + ->method('getProductId') + ->will($this->returnValue(1)); + $this->comparedMock->expects($this->any()) + ->method('getProductId') + ->will($this->returnValue(1)); + + $this->itemMock->expects($this->once())->method('getOptions')->willReturn([ + $this->getOptionMock('option-1', serialize([ + 'non-empty-option' => 'test', + 'empty_option' => '' + ])) + ]); + $this->comparedMock->expects($this->once())->method('getOptions')->willReturn([ + $this->getOptionMock('option-1', serialize([ + 'non-empty-option' => 'test' + ])) + ]); + + $this->assertTrue($this->helper->compare($this->itemMock, $this->comparedMock)); + } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php index cd2a56b3b9d7b414b24530e17dbf409dfcf2fe14..2664e7ef21ad76da3bfd6b844caf42d8bbdb8900 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php @@ -124,7 +124,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $quoteFactoryMock; + private $quoteIdMock; /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -238,7 +238,6 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase ); $this->quoteFactoryMock = $this->getMock('\Magento\Quote\Model\QuoteFactory', ['create'], [], '', false); - $this->model = $objectManager->getObject( '\Magento\Quote\Model\QuoteManagement', [ @@ -264,6 +263,12 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase 'quoteFactory' => $this->quoteFactoryMock ] ); + + // Set the new dependency + $this->quoteIdMock = $this->getMock('Magento\Quote\Model\QuoteIdMask', [], [], '', false); + $quoteIdFactoryMock = $this->getMock(\Magento\Quote\Model\QuoteIdMaskFactory::class, ['create'], [], '', false); + $this->setPropertyValue($this->model, 'quoteIdMaskFactory', $quoteIdFactoryMock); + } public function testCreateEmptyCartAnonymous() @@ -508,6 +513,13 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $customerId = 455; $storeId = 5; + $this->getPropertyValue($this->model, 'quoteIdMaskFactory') + ->expects($this->once()) + ->method('create') + ->willReturn($this->quoteIdMock); + $this->quoteIdMock->expects($this->once())->method('load')->with($cartId, 'quote_id')->willReturnSelf(); + $this->quoteIdMock->expects($this->once())->method('getId')->willReturn(10); + $this->quoteIdMock->expects($this->once())->method('delete'); $quoteMock = $this->getMock( '\Magento\Quote\Model\Quote', ['getCustomerId', 'setCustomer', 'setCustomerIsGuest'], @@ -739,7 +751,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $this->checkoutSessionMock->expects($this->once())->method('setLastOrderId')->with($orderId); $this->checkoutSessionMock->expects($this->once())->method('setLastRealOrderId')->with($orderIncrementId); $this->checkoutSessionMock->expects($this->once())->method('setLastOrderStatus')->with($orderStatus); - + $this->assertEquals($orderId, $service->placeOrder($cartId)); } @@ -935,7 +947,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $order = $this->getMock( 'Magento\Sales\Model\Order', ['setShippingAddress', 'getAddressesCollection', 'getAddresses', 'getBillingAddress', 'addAddresses', - 'setBillingAddress', 'setAddresses', 'setPayment', 'setItems', 'setQuoteId'], + 'setBillingAddress', 'setAddresses', 'setPayment', 'setItems', 'setQuoteId'], [], '', false @@ -979,4 +991,37 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase ->willReturn($cartMock); $this->assertEquals($cartMock, $this->model->getCartForCustomer($customerId)); } + + /** + * Get any object property value. + * + * @param $object + * @param $property + * @return mixed + */ + protected function getPropertyValue($object, $property) + { + $reflection = new \ReflectionClass(get_class($object)); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object); + } + + /** + * Set object property value. + * + * @param $object + * @param $property + * @param $value + */ + protected function setPropertyValue(&$object, $property, $value) + { + $reflection = new \ReflectionClass(get_class($object)); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + + return $object; + } } diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js index b636a613d622ffe50570117408c6be86b414df0a..606fe4013ea762c5460c49202907463d7c7952f2 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js @@ -13,26 +13,24 @@ define( 'jquery', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/resource-url-manager', - 'Magento_Checkout/js/model/payment-service', 'Magento_Checkout/js/model/error-processor', 'Magento_SalesRule/js/model/payment/discount-messages', 'mage/storage', - 'Magento_Checkout/js/action/get-totals', 'mage/translate', - 'Magento_Checkout/js/model/payment/method-list' + 'Magento_Checkout/js/action/get-payment-information', + 'Magento_Checkout/js/model/totals' ], function ( ko, $, quote, urlManager, - paymentService, errorProcessor, messageContainer, storage, - getTotalsAction, $t, - paymentMethodList + getPaymentInformationAction, + totals ) { 'use strict'; return function (couponCode, isApplied, isLoading) { @@ -49,11 +47,10 @@ define( var deferred = $.Deferred(); isLoading(false); isApplied(true); - getTotalsAction([], deferred); - $.when(deferred).done(function() { - paymentService.setPaymentMethods( - paymentMethodList() - ); + totals.isLoading(true); + getPaymentInformationAction(deferred); + $.when(deferred).done(function () { + totals.isLoading(false); }); messageContainer.addSuccessMessage({'message': message}); } @@ -61,6 +58,7 @@ define( ).fail( function (response) { isLoading(false); + totals.isLoading(false); errorProcessor.process(response, messageContainer); } ); diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php index c67470fd69ae592503f7b18dba7eee920ba133a1..a201724eaaa6824e9c0500b2108699be44e33285 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php @@ -70,9 +70,9 @@ class GuestCartManagementTest extends WebapiAbstract $quote = $this->objectManager->create('Magento\Quote\Model\Quote')->load('test01', 'reserved_order_id'); $cartId = $quote->getId(); /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create('Magento\Quote\Model\QuoteIdMaskFactory') - ->create(); + $quoteIdMaskFactory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Quote\Model\QuoteIdMaskFactory'); + $quoteIdMask = $quoteIdMaskFactory->create(); $quoteIdMask->load($cartId, 'quote_id'); //Use masked cart Id $cartId = $quoteIdMask->getMaskedId(); @@ -110,6 +110,7 @@ class GuestCartManagementTest extends WebapiAbstract $this->assertEquals($customer->getId(), $quote->getCustomerId()); $this->assertEquals($customer->getFirstname(), $quote->getCustomerFirstname()); $this->assertEquals($customer->getLastname(), $quote->getCustomerLastname()); + $this->assertNull($quoteIdMaskFactory->create()->load($cartId, 'masked_id')->getId()); } /**