From 8f014b145f1949cfca315f9019e4d63e67a4de9a Mon Sep 17 00:00:00 2001 From: Sergey Semenov Date: Thu, 12 Nov 2015 19:03:30 +0200 Subject: [PATCH 01/79] MAGETWO-45027: CSRF on Removing Item from Wishlist --- .../Wishlist/Controller/Index/Remove.php | 24 ++++++++--- .../Test/Unit/Controller/Index/RemoveTest.php | 41 ++++++++++++++++++- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Wishlist/Controller/Index/Remove.php b/app/code/Magento/Wishlist/Controller/Index/Remove.php index 7d7b396535b48..20c9da8e4bff1 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Remove.php +++ b/app/code/Magento/Wishlist/Controller/Index/Remove.php @@ -6,25 +6,35 @@ namespace Magento\Wishlist\Controller\Index; use Magento\Framework\App\Action; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Controller\ResultFactory; +use Magento\Wishlist\Controller\WishlistProviderInterface; class Remove extends \Magento\Wishlist\Controller\AbstractIndex { /** - * @var \Magento\Wishlist\Controller\WishlistProviderInterface + * @var WishlistProviderInterface */ protected $wishlistProvider; + /** + * @var Validator + */ + protected $formKeyValidator; + /** * @param Action\Context $context - * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider + * @param WishlistProviderInterface $wishlistProvider + * @param Validator $formKeyValidator */ public function __construct( Action\Context $context, - \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider + WishlistProviderInterface $wishlistProvider, + Validator $formKeyValidator ) { $this->wishlistProvider = $wishlistProvider; + $this->formKeyValidator = $formKeyValidator; parent::__construct($context); } @@ -36,6 +46,12 @@ public function __construct( */ public function execute() { + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + if (!$this->formKeyValidator->validate($this->getRequest())) { + return $resultRedirect->setPath('*/*/'); + } + $id = (int)$this->getRequest()->getParam('item'); $item = $this->_objectManager->create('Magento\Wishlist\Model\Item')->load($id); if (!$item->getId()) { @@ -68,8 +84,6 @@ public function execute() } else { $redirectUrl = $this->_redirect->getRedirectUrl($this->_url->getUrl('*/*')); } - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $resultRedirect->setUrl($redirectUrl); return $resultRedirect; } diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php index 6b2a6b9a5959f..6bd0bfd1418b3 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php @@ -54,6 +54,11 @@ class RemoveTest extends \PHPUnit_Framework_TestCase */ protected $resultRedirectMock; + /** + * @var \Magento\Framework\Data\Form\FormKey\Validator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $formKeyValidator; + protected function setUp() { $this->context = $this->getMock('Magento\Framework\App\Action\Context', [], [], '', false); @@ -74,6 +79,10 @@ protected function setUp() ->method('create') ->with(ResultFactory::TYPE_REDIRECT, []) ->willReturn($this->resultRedirectMock); + + $this->formKeyValidator = $this->getMockBuilder('Magento\Framework\Data\Form\FormKey\Validator') + ->disableOriginalConstructor() + ->getMock(); } public function tearDown() @@ -130,10 +139,40 @@ protected function prepareContext() public function getController() { $this->prepareContext(); + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + return new \Magento\Wishlist\Controller\Index\Remove( $this->context, - $this->wishlistProvider + $this->wishlistProvider, + $this->formKeyValidator + ); + } + + public function testExecuteWithInvalidFormKey() + { + $this->prepareContext(); + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(false); + + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('*/*/') + ->willReturnSelf(); + + $controller = new \Magento\Wishlist\Controller\Index\Remove( + $this->context, + $this->wishlistProvider, + $this->formKeyValidator ); + + $this->assertSame($this->resultRedirectMock, $controller->execute()); } /** From 7dc9e60ac6d25da4db6ae518583293cdb3740540 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpenko Date: Fri, 13 Nov 2015 11:34:15 +0200 Subject: [PATCH 02/79] MAGETWO-43188: Unable to rename theme by editing theme.xml file --- .../Theme/Model/Theme/Plugin/Registration.php | 33 +++++++++++- .../Model/Theme/Plugin/RegistrationTest.php | 53 +++++++++++++++---- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php index 0f469442c15de..f7c7c84335d69 100644 --- a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php +++ b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php @@ -11,12 +11,21 @@ use Magento\Framework\Exception\LocalizedException; use Psr\Log\LoggerInterface; use Magento\Framework\App\State as AppState; +use Magento\Theme\Model\Theme\Collection as ThemeCollection; +use Magento\Theme\Model\ResourceModel\Theme\Collection as ThemeLoader; +use Magento\Framework\Config\Theme; class Registration { /** @var ThemeRegistration */ protected $themeRegistration; + /** @var ThemeCollection */ + protected $themeCollection; + + /** @var ThemeLoader */ + protected $themeLoader; + /** @var LoggerInterface */ protected $logger; @@ -25,21 +34,27 @@ class Registration /** * @param ThemeRegistration $themeRegistration + * @param ThemeCollection $themeCollection + * @param ThemeLoader $themeLoader * @param LoggerInterface $logger * @param AppState $appState */ public function __construct( ThemeRegistration $themeRegistration, + ThemeCollection $themeCollection, + ThemeLoader $themeLoader, LoggerInterface $logger, AppState $appState ) { $this->themeRegistration = $themeRegistration; + $this->themeCollection = $themeCollection; + $this->themeLoader = $themeLoader; $this->logger = $logger; $this->appState = $appState; } /** - * Add new theme from filesystem + * Add new theme from filesystem and update exists * * @param AbstractAction $subject * @param RequestInterface $request @@ -54,9 +69,25 @@ public function beforeDispatch( try { if ($this->appState->getMode() != AppState::MODE_PRODUCTION) { $this->themeRegistration->register(); + $this->updateThemeData(); } } catch (LocalizedException $e) { $this->logger->critical($e); } } + + protected function updateThemeData() + { + $themesData = $this->themeCollection->loadData(); + /** @var \Magento\Theme\Model\Theme $themeData */ + foreach ($themesData as $themeData) { + /** @var \Magento\Theme\Model\Theme $theme */ + $theme = $this->themeLoader->getThemeByFullPath( + $themeData->getArea() + . Theme::THEME_PATH_SEPARATOR + . $themeData->getThemePath() + ); + $theme->addData($themeData->toArray())->save(); + } + } } diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php index faf0bdf983871..05427a9aae9f4 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php @@ -26,6 +26,15 @@ class RegistrationTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject */ protected $appState; + /** @var \Magento\Theme\Model\Theme\Collection|\PHPUnit_Framework_MockObject_MockObject */ + protected $themeCollection; + + /** @var \Magento\Theme\Model\ResourceModel\Theme\Collection|\PHPUnit_Framework_MockObject_MockObject */ + protected $themeLoader; + + /** @var Registration */ + protected $plugin; + public function setUp() { $this->themeRegistration = $this->getMock('Magento\Theme\Model\Theme\Registration', [], [], '', false); @@ -33,24 +42,50 @@ public function setUp() $this->abstractAction = $this->getMockForAbstractClass('Magento\Backend\App\AbstractAction', [], '', false); $this->request = $this->getMockForAbstractClass('Magento\Framework\App\RequestInterface', [], '', false); $this->appState = $this->getMock('Magento\Framework\App\State', [], [], '', false); + $this->themeCollection = $this->getMock('Magento\Theme\Model\Theme\Collection', [], [], '', false); + $this->themeLoader = $this->getMock('Magento\Theme\Model\ResourceModel\Theme\Collection', [], [], '', false); + $this->plugin = new Registration( + $this->themeRegistration, + $this->themeCollection, + $this->themeLoader, + $this->logger, + $this->appState + ); } public function testBeforeDispatch() { + $theme = $this->getMock('Magento\Theme\Model\Theme', [], [], '', false); $this->appState->expects($this->once())->method('getMode')->willReturn('default'); $this->themeRegistration->expects($this->once())->method('register'); - $this->logger->expects($this->never())->method('critical'); - $object = new Registration($this->themeRegistration, $this->logger, $this->appState); - $object->beforeDispatch($this->abstractAction, $this->request); + $this->themeCollection->expects($this->once())->method('loadData')->willReturn([$theme]); + $theme->expects($this->once())->method('getArea')->willReturn('frontend'); + $theme->expects($this->once())->method('getThemePath')->willReturn('Magento/luma'); + $this->themeLoader->expects($this->once()) + ->method('getThemeByFullPath') + ->with('frontend/Magento/luma') + ->willReturn($theme); + $theme->expects($this->once()) + ->method('toArray') + ->willReturn([ + 'title' => 'Magento Luma' + ]); + $theme->expects($this->once()) + ->method('addData') + ->with([ + 'title' => 'Magento Luma' + ]) + ->willReturnSelf(); + $theme->expects($this->once()) + ->method('save'); + + $this->plugin->beforeDispatch($this->abstractAction, $this->request); } public function testBeforeDispatchWithProductionMode() { $this->appState->expects($this->once())->method('getMode')->willReturn('production'); - $this->themeRegistration->expects($this->never())->method('register'); - $this->logger->expects($this->never())->method('critical'); - $object = new Registration($this->themeRegistration, $this->logger, $this->appState); - $object->beforeDispatch($this->abstractAction, $this->request); + $this->plugin->beforeDispatch($this->abstractAction, $this->request); } public function testBeforeDispatchWithException() @@ -58,7 +93,7 @@ public function testBeforeDispatchWithException() $exception = new LocalizedException(new Phrase('Phrase')); $this->themeRegistration->expects($this->once())->method('register')->willThrowException($exception); $this->logger->expects($this->once())->method('critical'); - $object = new Registration($this->themeRegistration, $this->logger, $this->appState); - $object->beforeDispatch($this->abstractAction, $this->request); + + $this->plugin->beforeDispatch($this->abstractAction, $this->request); } } From 1bf4d46ed33c03a566ae74a83a2da74f5b459bd0 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpenko Date: Fri, 13 Nov 2015 11:36:40 +0200 Subject: [PATCH 03/79] MAGETWO-43188: Unable to rename theme by editing theme.xml file --- app/code/Magento/Theme/Model/Theme/Plugin/Registration.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php index f7c7c84335d69..8b470d1ccb8f6 100644 --- a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php +++ b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php @@ -76,6 +76,11 @@ public function beforeDispatch( } } + /** + * Update theme data + * + * @return void + */ protected function updateThemeData() { $themesData = $this->themeCollection->loadData(); From 73e46c1a0c2b3b8d23b89fdc2b563653652f8261 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpenko Date: Fri, 13 Nov 2015 11:40:21 +0200 Subject: [PATCH 04/79] MAGETWO-43188: Unable to rename theme by editing theme.xml file --- app/code/Magento/Theme/Model/Theme/Plugin/Registration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php index 8b470d1ccb8f6..7cc11c6ebd12f 100644 --- a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php +++ b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php @@ -54,7 +54,7 @@ public function __construct( } /** - * Add new theme from filesystem and update exists + * Add new theme from filesystem and update existing * * @param AbstractAction $subject * @param RequestInterface $request From 300465c11c48565224c34b86caf46d7bb9e291cb Mon Sep 17 00:00:00 2001 From: Oleksandr Karpenko Date: Fri, 13 Nov 2015 15:05:42 +0200 Subject: [PATCH 05/79] MAGETWO-43188: Unable to rename theme by editing theme.xml file --- app/code/Magento/Theme/Model/Theme/Plugin/Registration.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php index 7cc11c6ebd12f..3d77e359bf2a0 100644 --- a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php +++ b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php @@ -15,6 +15,9 @@ use Magento\Theme\Model\ResourceModel\Theme\Collection as ThemeLoader; use Magento\Framework\Config\Theme; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Registration { /** @var ThemeRegistration */ From 23da851814315d7f3290bee60e07d8a927436537 Mon Sep 17 00:00:00 2001 From: Sergey Semenov Date: Fri, 13 Nov 2015 18:04:32 +0200 Subject: [PATCH 06/79] MAGETWO-45027: CSRF on Removing Item from Wishlist --- .../Magento/Wishlist/Controller/Index/Add.php | 21 +++++-- .../Wishlist/Controller/Index/Cart.php | 18 +++++- .../Wishlist/Controller/Index/Fromcart.php | 20 +++++-- .../Controller/Index/UpdateItemOptions.php | 33 ++++++++--- .../Test/Unit/Controller/Index/CartTest.php | 58 ++++++++++++++++++- .../Unit/Controller/Index/FromcartTest.php | 48 ++++++++++++++- .../Index/UpdateItemOptionsTest.php | 41 ++++++++++++- 7 files changed, 215 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Wishlist/Controller/Index/Add.php b/app/code/Magento/Wishlist/Controller/Index/Add.php index e3bd753ce7589..61820f634c4cf 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Add.php +++ b/app/code/Magento/Wishlist/Controller/Index/Add.php @@ -7,6 +7,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\App\Action; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Controller\ResultFactory; @@ -31,22 +32,30 @@ class Add extends \Magento\Wishlist\Controller\AbstractIndex */ protected $productRepository; + /** + * @var Validator + */ + protected $formKeyValidator; + /** * @param Action\Context $context * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider * @param ProductRepositoryInterface $productRepository + * @param Validator $formKeyValidator */ public function __construct( Action\Context $context, \Magento\Customer\Model\Session $customerSession, \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider, - ProductRepositoryInterface $productRepository + ProductRepositoryInterface $productRepository, + Validator $formKeyValidator ) { $this->_customerSession = $customerSession; $this->wishlistProvider = $wishlistProvider; - parent::__construct($context); $this->productRepository = $productRepository; + $this->formKeyValidator = $formKeyValidator; + parent::__construct($context); } /** @@ -60,6 +69,12 @@ public function __construct( */ public function execute() { + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + if (!$this->formKeyValidator->validate($this->getRequest())) { + return $resultRedirect->setPath('*/'); + } + $wishlist = $this->wishlistProvider->getWishlist(); if (!$wishlist) { throw new NotFoundException(__('Page not found.')); @@ -75,8 +90,6 @@ public function execute() } $productId = isset($requestParams['product']) ? (int)$requestParams['product'] : null; - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!$productId) { $resultRedirect->setPath('*/'); return $resultRedirect; diff --git a/app/code/Magento/Wishlist/Controller/Index/Cart.php b/app/code/Magento/Wishlist/Controller/Index/Cart.php index 2be0f7dc01fb4..9240954b302bf 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Cart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Cart.php @@ -59,17 +59,23 @@ class Cart extends \Magento\Wishlist\Controller\AbstractIndex */ protected $helper; + /** + * @var \Magento\Framework\Data\Form\FormKey\Validator + */ + protected $formKeyValidator; + /** * @param Action\Context $context * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider * @param \Magento\Wishlist\Model\LocaleQuantityProcessor $quantityProcessor * @param \Magento\Wishlist\Model\ItemFactory $itemFactory * @param \Magento\Checkout\Model\Cart $cart - * @param \Magento\Wishlist\Model\Item\OptionFactory $ + * @param \Magento\Wishlist\Model\Item\OptionFactory $optionFactory * @param \Magento\Catalog\Helper\Product $productHelper * @param \Magento\Framework\Escaper $escaper * @param \Magento\Wishlist\Helper\Data $helper * @param \Magento\Checkout\Helper\Cart $cartHelper + * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -82,7 +88,8 @@ public function __construct( \Magento\Catalog\Helper\Product $productHelper, \Magento\Framework\Escaper $escaper, \Magento\Wishlist\Helper\Data $helper, - \Magento\Checkout\Helper\Cart $cartHelper + \Magento\Checkout\Helper\Cart $cartHelper, + \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator ) { $this->wishlistProvider = $wishlistProvider; $this->quantityProcessor = $quantityProcessor; @@ -93,6 +100,7 @@ public function __construct( $this->escaper = $escaper; $this->helper = $helper; $this->cartHelper = $cartHelper; + $this->formKeyValidator = $formKeyValidator; parent::__construct($context); } @@ -108,9 +116,13 @@ public function __construct( */ public function execute() { - $itemId = (int)$this->getRequest()->getParam('item'); /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + if (!$this->formKeyValidator->validate($this->getRequest())) { + return $resultRedirect->setPath('*/*/'); + } + + $itemId = (int)$this->getRequest()->getParam('item'); /* @var $item \Magento\Wishlist\Model\Item */ $item = $this->itemFactory->create()->load($itemId); if (!$item->getId()) { diff --git a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php index c5ff1ef21c27a..9afecb231788b 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php @@ -9,6 +9,7 @@ use Magento\Checkout\Model\Cart as CheckoutCart; use Magento\Customer\Model\Session; use Magento\Framework\App\Action; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Escaper; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Exception\LocalizedException; @@ -46,6 +47,11 @@ class Fromcart extends \Magento\Wishlist\Controller\AbstractIndex */ protected $escaper; + /** + * @var Validator + */ + protected $formKeyValidator; + /** * @param Action\Context $context * @param WishlistProviderInterface $wishlistProvider @@ -53,6 +59,7 @@ class Fromcart extends \Magento\Wishlist\Controller\AbstractIndex * @param CheckoutCart $cart * @param CartHelper $cartHelper * @param Escaper $escaper + * @param Validator $formKeyValidator */ public function __construct( Action\Context $context, @@ -60,13 +67,15 @@ public function __construct( WishlistHelper $wishlistHelper, CheckoutCart $cart, CartHelper $cartHelper, - Escaper $escaper + Escaper $escaper, + Validator $formKeyValidator ) { $this->wishlistProvider = $wishlistProvider; $this->wishlistHelper = $wishlistHelper; $this->cart = $cart; $this->cartHelper = $cartHelper; $this->escaper = $escaper; + $this->formKeyValidator = $formKeyValidator; parent::__construct($context); } @@ -79,6 +88,12 @@ public function __construct( */ public function execute() { + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + if (!$this->formKeyValidator->validate($this->getRequest())) { + return $resultRedirect->setPath('*/*/'); + } + $wishlist = $this->wishlistProvider->getWishlist(); if (!$wishlist) { throw new NotFoundException(__('Page not found.')); @@ -112,9 +127,6 @@ public function execute() } catch (\Exception $e) { $this->messageManager->addExceptionMessage($e, __('We can\'t move the item to the wish list.')); } - - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); return $resultRedirect->setUrl($this->cartHelper->getCartUrl()); } } diff --git a/app/code/Magento/Wishlist/Controller/Index/UpdateItemOptions.php b/app/code/Magento/Wishlist/Controller/Index/UpdateItemOptions.php index 3c8dcedd80426..c1cb5cbb88019 100644 --- a/app/code/Magento/Wishlist/Controller/Index/UpdateItemOptions.php +++ b/app/code/Magento/Wishlist/Controller/Index/UpdateItemOptions.php @@ -6,19 +6,22 @@ namespace Magento\Wishlist\Controller\Index; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\Session; use Magento\Framework\App\Action; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Controller\ResultFactory; +use Magento\Wishlist\Controller\WishlistProviderInterface; class UpdateItemOptions extends \Magento\Wishlist\Controller\AbstractIndex { /** - * @var \Magento\Wishlist\Controller\WishlistProviderInterface + * @var WishlistProviderInterface */ protected $wishlistProvider; /** - * @var \Magento\Customer\Model\Session + * @var Session */ protected $_customerSession; @@ -27,22 +30,30 @@ class UpdateItemOptions extends \Magento\Wishlist\Controller\AbstractIndex */ protected $productRepository; + /** + * @var Validator + */ + protected $formKeyValidator; + /** * @param Action\Context $context - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider + * @param Session $customerSession + * @param WishlistProviderInterface $wishlistProvider * @param ProductRepositoryInterface $productRepository + * @param Validator $formKeyValidator */ public function __construct( Action\Context $context, - \Magento\Customer\Model\Session $customerSession, - \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider, - ProductRepositoryInterface $productRepository + Session $customerSession, + WishlistProviderInterface $wishlistProvider, + ProductRepositoryInterface $productRepository, + Validator $formKeyValidator ) { $this->_customerSession = $customerSession; $this->wishlistProvider = $wishlistProvider; - parent::__construct($context); $this->productRepository = $productRepository; + $this->formKeyValidator = $formKeyValidator; + parent::__construct($context); } /** @@ -52,9 +63,13 @@ public function __construct( */ public function execute() { - $productId = (int)$this->getRequest()->getParam('product'); /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + if (!$this->formKeyValidator->validate($this->getRequest())) { + return $resultRedirect->setPath('*/*/'); + } + + $productId = (int)$this->getRequest()->getParam('product'); if (!$productId) { $resultRedirect->setPath('*/'); return $resultRedirect; diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php index cdd8ea3b33372..a26ae8cb29ab2 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php @@ -110,6 +110,11 @@ class CartTest extends \PHPUnit_Framework_TestCase */ protected $resultJsonMock; + /** + * @var \Magento\Framework\Data\Form\FormKey\Validator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $formKeyValidator; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -216,6 +221,9 @@ protected function setUp() ] ); + $this->formKeyValidator = $this->getMockBuilder('Magento\Framework\Data\Form\FormKey\Validator') + ->disableOriginalConstructor() + ->getMock(); $this->model = new Cart( $this->contextMock, @@ -227,14 +235,35 @@ protected function setUp() $this->productHelperMock, $this->escaperMock, $this->helperMock, - $this->cartHelperMock + $this->cartHelperMock, + $this->formKeyValidator ); } + public function testExecuteWithInvalidFormKey() + { + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(false); + + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('*/*/') + ->willReturnSelf(); + + $this->assertSame($this->resultRedirectMock, $this->model->execute()); + } + public function testExecuteWithNoItem() { $itemId = false; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $itemMock = $this->getMockBuilder('Magento\Wishlist\Model\Item') ->disableOriginalConstructor() ->getMock(); @@ -267,6 +296,11 @@ public function testExecuteWithNoWishlist() $itemId = 2; $wishlistId = 1; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $itemMock = $this->getMockBuilder('Magento\Wishlist\Model\Item') ->disableOriginalConstructor() ->setMethods(['load', 'getId', 'getWishlistId']) @@ -306,7 +340,12 @@ public function testExecuteWithNoWishlist() public function testExecuteWithQuantityArray() { $refererUrl = $this->prepareExecuteWithQuantityArray(); - + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $this->resultRedirectMock->expects($this->once()) ->method('setUrl') ->with($refererUrl) @@ -319,6 +358,11 @@ public function testExecuteWithQuantityArrayAjax() { $refererUrl = $this->prepareExecuteWithQuantityArray(true); + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $this->resultJsonMock->expects($this->once()) ->method('setData') ->with(['backUrl' => $refererUrl]) @@ -554,6 +598,11 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() $options = [5 => 'option']; $params = ['item' => $itemId, 'qty' => $qty]; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $itemMock = $this->getMockBuilder('Magento\Wishlist\Model\Item') ->disableOriginalConstructor() ->setMethods( @@ -715,6 +764,11 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() $options = [5 => 'option']; $params = ['item' => $itemId, 'qty' => $qty]; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $itemMock = $this->getMockBuilder('Magento\Wishlist\Model\Item') ->disableOriginalConstructor() ->setMethods( diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php index 58109d6dc3d25..6c8124af90800 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php @@ -11,6 +11,7 @@ use Magento\Framework\App\Request\Http; use Magento\Framework\Controller\Result\Redirect as ResultRedirect; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\DataObject; use Magento\Framework\Escaper; use Magento\Framework\Message\Manager as MessageManager; @@ -78,6 +79,11 @@ class FromcartTest extends \PHPUnit_Framework_TestCase */ protected $resultRedirect; + /** + * @var Validator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $formKeyValidator; + protected function setUp() { $this->prepareContext(); @@ -101,22 +107,47 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->formKeyValidator = $this->getMockBuilder('Magento\Framework\Data\Form\FormKey\Validator') + ->disableOriginalConstructor() + ->getMock(); + $this->controller = new Fromcart( $this->context, $this->wishlistProvider, $this->wishlistHelper, $this->cart, $this->cartHelper, - $this->escaper + $this->escaper, + $this->formKeyValidator ); } + public function testExecuteWithInvalidFormKey() + { + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(false); + + $this->resultRedirect->expects($this->once()) + ->method('setPath') + ->with('*/*/') + ->willReturnSelf(); + + $this->assertSame($this->resultRedirect, $this->controller->execute()); + } + /** * @expectedException \Magento\Framework\Exception\NotFoundException * @expectedExceptionMessage Page not found */ public function testExecutePageNotFound() { + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + $this->wishlistProvider->expects($this->once()) ->method('getWishlist') ->willReturn(null); @@ -129,6 +160,11 @@ public function testExecuteNoCartItem() $itemId = 1; $cartUrl = 'cart_url'; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + $wishlistMock = $this->getMockBuilder('Magento\Wishlist\Model\Wishlist') ->disableOriginalConstructor() ->getMock(); @@ -179,6 +215,11 @@ public function testExecute() $productId = 1; $productName = 'product_name'; + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + $dataObjectMock = $this->getMockBuilder('Magento\Framework\DataObject') ->disableOriginalConstructor() ->getMock(); @@ -244,6 +285,11 @@ public function testExecuteWithException() $exceptionMessage = 'exception_message'; $exception = new \Exception($exceptionMessage); + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + $wishlistMock = $this->getMockBuilder('Magento\Wishlist\Model\Wishlist') ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateItemOptionsTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateItemOptionsTest.php index f70306b60257a..ec44ab755bee1 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateItemOptionsTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateItemOptionsTest.php @@ -67,6 +67,11 @@ class UpdateItemOptionsTest extends \PHPUnit_Framework_TestCase */ protected $resultRedirectMock; + /** + * @var \Magento\Framework\Data\Form\FormKey\Validator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $formKeyValidator; + /** * SetUp method * @@ -94,6 +99,10 @@ protected function setUp() ->method('create') ->with(ResultFactory::TYPE_REDIRECT, []) ->willReturn($this->resultRedirectMock); + + $this->formKeyValidator = $this->getMockBuilder('Magento\Framework\Data\Form\FormKey\Validator') + ->disableOriginalConstructor() + ->getMock(); } /** @@ -161,12 +170,42 @@ public function prepareContext() protected function getController() { $this->prepareContext(); + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(true); + return new \Magento\Wishlist\Controller\Index\UpdateItemOptions( $this->context, $this->customerSession, $this->wishlistProvider, - $this->productRepository + $this->productRepository, + $this->formKeyValidator + ); + } + + public function testExecuteWithInvalidFormKey() + { + $this->prepareContext(); + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->request) + ->willReturn(false); + + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('*/*/') + ->willReturnSelf(); + + $controller = new \Magento\Wishlist\Controller\Index\Remove( + $this->context, + $this->wishlistProvider, + $this->formKeyValidator ); + + $this->assertSame($this->resultRedirectMock, $controller->execute()); } /** From 0aeac1bf8592dc99637e1eb829d1a91c4b49e36a Mon Sep 17 00:00:00 2001 From: Oleksandr Karpenko Date: Fri, 13 Nov 2015 18:39:16 +0200 Subject: [PATCH 07/79] MAGETWO-43188: Unable to rename theme by editing theme.xml file --- .../Theme/Model/Theme/Plugin/Registration.php | 7 ++++ .../Model/Theme/Plugin/RegistrationTest.php | 34 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php index 3d77e359bf2a0..947d7ceca6d05 100644 --- a/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php +++ b/app/code/Magento/Theme/Model/Theme/Plugin/Registration.php @@ -89,6 +89,13 @@ protected function updateThemeData() $themesData = $this->themeCollection->loadData(); /** @var \Magento\Theme\Model\Theme $themeData */ foreach ($themesData as $themeData) { + if ($themeData->getParentTheme()) { + $parentTheme = $this->themeLoader->getThemeByFullPath( + $themeData->getParentTheme()->getFullPath() + ); + $themeData->setParentId($parentTheme->getId()); + } + /** @var \Magento\Theme\Model\Theme $theme */ $theme = $this->themeLoader->getThemeByFullPath( $themeData->getArea() diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php index 05427a9aae9f4..3512086977d35 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/Plugin/RegistrationTest.php @@ -55,16 +55,42 @@ public function setUp() public function testBeforeDispatch() { - $theme = $this->getMock('Magento\Theme\Model\Theme', [], [], '', false); + $theme = $this->getMock( + 'Magento\Theme\Model\Theme', + [ + 'setParentId', + 'getArea', + 'getThemePath', + 'getParentTheme', + 'getId', + 'getFullPath', + 'toArray', + 'addData', + 'save', + ], + [], + '', + false + ); $this->appState->expects($this->once())->method('getMode')->willReturn('default'); $this->themeRegistration->expects($this->once())->method('register'); $this->themeCollection->expects($this->once())->method('loadData')->willReturn([$theme]); $theme->expects($this->once())->method('getArea')->willReturn('frontend'); $theme->expects($this->once())->method('getThemePath')->willReturn('Magento/luma'); - $this->themeLoader->expects($this->once()) + $theme->expects($this->exactly(2))->method('getParentTheme')->willReturnSelf(); + $theme->expects($this->once())->method('getId')->willReturn(1); + $theme->expects($this->once())->method('getFullPath')->willReturn('frontend/Magento/blank'); + $theme->expects($this->once())->method('setParentId')->with(1); + $this->themeLoader->expects($this->exactly(2)) ->method('getThemeByFullPath') - ->with('frontend/Magento/luma') - ->willReturn($theme); + ->withConsecutive( + ['frontend/Magento/blank'], + ['frontend/Magento/luma'] + ) + ->will($this->onConsecutiveCalls( + $theme, + $theme + )); $theme->expects($this->once()) ->method('toArray') ->willReturn([ From 766aa845aa16a2a68827722f0d30450a7b4efc0b Mon Sep 17 00:00:00 2001 From: Sergey Semenov Date: Mon, 16 Nov 2015 19:34:45 +0200 Subject: [PATCH 08/79] MAGETWO-45027: CSRF on Removing Item from Wishlist --- .../Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php index 6bd0bfd1418b3..9ade108968009 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php @@ -7,6 +7,9 @@ use Magento\Framework\Controller\ResultFactory; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class RemoveTest extends \PHPUnit_Framework_TestCase { /** From 08b6a7fa8b9f10b0c73ff9c4cebc08e048b8d7a3 Mon Sep 17 00:00:00 2001 From: Olga Matviienko Date: Tue, 17 Nov 2015 12:45:16 +0200 Subject: [PATCH 09/79] MAGETWO-45095: Product base images aren't centered in Wish List on Storefront - Formatting and sorting --- .../web/css/source/module/_listings.less | 425 ++++++++++-------- .../web/css/source/module/_listings.less | 341 +++++++------- 2 files changed, 407 insertions(+), 359 deletions(-) diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less index 23ef879b1f6c4..018a0afdf2ed1 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less @@ -20,230 +20,228 @@ // // -// Common -//-------------------------------------- +// Common +// _____________________________________________ & when (@media-common = true) { - -// Product Lists -.products { - margin: @indent__l 0; -} - -.product { - &-items { - &:extend(.abs-reset-list all); + // Product Lists + .products { + margin: @indent__l 0; } - &-item { - vertical-align: top; - .products-grid & { - width: 100%/2; - display: inline-block; - } - &:extend(.abs-add-box-sizing all); - - &-name { - &:extend(.abs-product-link all); - display: block; - margin: @indent__xs 0; - word-wrap: break-word; - -webkit-hyphens: auto; - -moz-hyphens: auto; - -ms-hyphens: auto; - hyphens: auto; + + .product { + &-items { + &:extend(.abs-reset-list all); } + &-item { + vertical-align: top; + .products-grid & { + width: 100%/2; + display: inline-block; + } + &:extend(.abs-add-box-sizing all); - &-info { - width: 152px; - max-width: 100%; - .page-products & { - width: 240px; + &-name { + &:extend(.abs-product-link all); + display: block; + margin: @indent__xs 0; + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; } - } - &-actions { - display: none; - .actions-secondary { - > .action { - &:extend(.abs-actions-addto-gridlist all); - &:before { - margin: 0; - } - span { - &:extend(.abs-visually-hidden all); + &-info { + width: 152px; + max-width: 100%; + .page-products & { + width: 240px; + } + } + + &-actions { + display: none; + .actions-secondary { + > .action { + &:extend(.abs-actions-addto-gridlist all); + &:before { + margin: 0; + } + span { + &:extend(.abs-visually-hidden all); + } } } } - } - &-description { - margin: @indent__m 0; - } - .product-reviews-summary { - .rating-summary { - margin: 0 4px 0 0; + &-description { + margin: @indent__m 0; } - .reviews-actions { - margin-top: 5px; - text-transform: lowercase; - font-size: @font-size__s; + .product-reviews-summary { + .rating-summary { + margin: 0 4px 0 0; + } + .reviews-actions { + font-size: @font-size__s; + margin-top: 5px; + text-transform: lowercase; + } } - } - .price-box { - margin: @indent__s 0 @indent__m; - .price { - .lib-font-size(14); - font-weight: bold; - } - .price-label { - font-size: @font-size__s; - &:after { - content: ":"; + .price-box { + margin: @indent__s 0 @indent__m; + .price { + .lib-font-size(14); + font-weight: bold; + } + .price-label { + font-size: @font-size__s; + &:after { + content: ':'; + } } } - } - .special-price, - .minimal-price { - .price { - .lib-font-size(14); - font-weight: bold; - } - .price-wrapper { - display: inline-block; + .special-price, + .minimal-price { + .price { + .lib-font-size(14); + font-weight: bold; + } + .price-wrapper { + display: inline-block; + } + .price-including-tax + .price-excluding-tax { + display: block; + } } - .price-including-tax + .price-excluding-tax { + + .special-price { display: block; } - } - .special-price { - display: block; - } - - .old-price { - .price { - font-weight: @font-weight__regular; + .old-price { + .price { + font-weight: @font-weight__regular; + } } - } - .minimal-price { - .price-container { - display: block; + .minimal-price { + .price-container { + display: block; + } } - } - .minimal-price-link { - margin-top: 5px; - .price-label { - .lib-css(color, @link__color); - .lib-font-size(14); - } - .price { - font-weight: @font-weight__regular; + .minimal-price-link { + margin-top: 5px; + .price-label { + .lib-css(color, @link__color); + .lib-font-size(14); + } + .price { + font-weight: @font-weight__regular; + } } - } - .minimal-price-link, - .price-excluding-tax, - .price-including-tax { - white-space: nowrap; - display: block; - } + .minimal-price-link, + .price-excluding-tax, + .price-including-tax { + display: block; + white-space: nowrap; + } - .price-from, - .price-to { - margin: 0; - } + .price-from, + .price-to { + margin: 0; + } - .tocompare { - .lib-icon-font-symbol( + .tocompare { + .lib-icon-font-symbol( @icon-compare-empty - ); - } + ); + } - .tocart { - white-space: nowrap; + .tocart { + white-space: nowrap; + } } } -} -.column.main { - .product { - &-items { - margin-left: -20px; - } - &-item { - padding-left: 20px; + .column.main { + .product { + &-items { + margin-left: -20px; + } + &-item { + padding-left: 20px; + } } } -} - -.price-container { - .price { - .lib-font-size(14); - } - .price-including-tax + .price-excluding-tax, - .weee { - margin-top: 5px; - } - - .price-including-tax + .price-excluding-tax, - .weee, - .price-including-tax + .price-excluding-tax .price, - .weee .price, - .weee + .price-excluding-tax:before, - .weee + .price-excluding-tax .price { - .lib-font-size(11); - } + .price-container { + .price { + .lib-font-size(14); + } - .weee { - &:before { - content: "("attr(data-label) ": "; + .price-including-tax + .price-excluding-tax, + .weee { + margin-top: 5px; } - &:after { - content: ")"; + + .price-including-tax + .price-excluding-tax, + .weee, + .price-including-tax + .price-excluding-tax .price, + .weee .price, + .weee + .price-excluding-tax:before, + .weee + .price-excluding-tax .price { + .lib-font-size(11); } - + .price-excluding-tax { + + .weee { &:before { - content: attr(data-label) ": "; + content: '('attr(data-label) ': '; + } + &:after { + content: ')'; + } + + .price-excluding-tax { + &:before { + content: attr(data-label) ': '; + } } } } -} -.products-list { - .product { - &-item { - display: table; - width: 100%; + .products-list { + .product { + &-item { + display: table; + width: 100%; - &-info { - display: table-row; - } - &-photo { - width: 1%; - padding: 0 @indent__l @indent__l 0; - vertical-align: top; - display: table-cell; - } - &-details { - vertical-align: top; - display: table-cell; + &-info { + display: table-row; + } + &-photo { + display: table-cell; + padding: 0 @indent__l @indent__l 0; + vertical-align: top; + width: 1%; + } + &-details { + display: table-cell; + vertical-align: top; + } } } + .product-image-wrapper { + &:extend(.abs-reset-image-wrapper all); + } } - .product-image-wrapper { - &:extend(.abs-reset-image-wrapper all); - } -} - } // // Mobile -// --------------------------------------------- +// _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) { .products-list .product { @@ -273,50 +271,95 @@ } .actions-primary + .actions-secondary { display: table-cell; - width: 50%; padding-left: 5px; white-space: nowrap; + width: 50%; > * { white-space: normal; } > .action { } } - .actions-primary { display: table-cell; } + .actions-primary { + display: table-cell; + } } } } - .products-grid .product-item { width: 100%/3 } - .page-layout-1column .products-grid .product-item { width: 100%/3 } - .page-layout-3columns .products-grid .product-item { width: 100%/3 } - .page-products .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/3 } + .products-grid .product-item { + width: 100%/3; + } + + .page-layout-1column .products-grid .product-item { + width: 100%/3; + } + + .page-layout-3columns .products-grid .product-item { + width: 100%/3; + } + + .page-products .products-grid .product-item { + width: 100%/3; + } + + .page-products.page-layout-1column .products-grid .product-item { + width: 100%/3; + } + + .page-products.page-layout-3columns .products-grid .product-item { + width: 100%/3; + } } // // Desktop -// --------------------------------------------- +// _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { - .page-products .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/4 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/2 } + .page-products .products-grid .product-item { + width: 100%/3; + } + + .page-products.page-layout-1column .products-grid .product-item { + width: 100%/4; + } + + .page-products.page-layout-3columns .products-grid .product-item { + width: 100%/2; + } } .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) { - .products-grid .product-item { width: 100%/5 } - .page-layout-1column .products-grid .product-item { width: 100%/6 } - .page-layout-3columns .products-grid .product-item { width: 100%/4 } - .page-products .products-grid .product-items { margin: 0; } + .products-grid .product-item { + width: 100%/5; + } + + .page-layout-1column .products-grid .product-item { + width: 100%/6; + } + + .page-layout-3columns .products-grid .product-item { + width: 100%/4; + } + + .page-products .products-grid .product-items { + margin: 0; + } + .page-products .products-grid .product-item { - width: 23.233%; margin-left: calc(~"(100% - 4 * 23.233%) / 3"); padding: 0; + width: 23.233%; &:nth-child(4n+1) { margin-left: 0; } } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/5 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/4 } + + .page-products.page-layout-1column .products-grid .product-item { + width: 100%/5; + } + + .page-products.page-layout-3columns .products-grid .product-item { + width: 100%/4; + } } diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 909549d36b2ce..55b61309bf5b4 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -20,199 +20,200 @@ // // -// Common -//-------------------------------------- +// Common +// _____________________________________________ & when (@media-common = true) { - -// Product Lists -.products { - margin: @indent__l 0; -} -.product { - &-items { - &:extend(.abs-reset-list all); + // Product Lists + .products { + margin: @indent__l 0; } - &-item { - vertical-align: top; - .products-grid & { - width: 100%/2; - display: inline-block; - } - &:extend(.abs-add-box-sizing all); - - &-name { - &:extend(.abs-product-link all); - display: block; - margin: @indent__xs 0; - word-wrap: break-word; - -webkit-hyphens: auto; - -moz-hyphens: auto; - -ms-hyphens: auto; - hyphens: auto; + .product { + &-items { + &:extend(.abs-reset-list all); } + &-item { + vertical-align: top; + .products-grid & { + width: 100%/2; + display: inline-block; + } + &:extend(.abs-add-box-sizing all); - &-info { - width: 152px; - max-width: 100%; - .page-products & { - width: 240px; + &-name { + &:extend(.abs-product-link all); + display: block; + margin: @indent__xs 0; + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; } - } - &-actions { - display: none; - .actions-secondary { - > .action { - &:extend(.abs-actions-addto all); - &:before { - margin: 0; - } - span { - &:extend(.abs-visually-hidden all); + &-info { + max-width: 100%; + width: 152px; + .page-products & { + width: 240px; + } + } + + &-actions { + display: none; + .actions-secondary { + > .action { + &:extend(.abs-actions-addto all); + &:before { + margin: 0; + } + span { + &:extend(.abs-visually-hidden all); + } } } } - } - &-description { - margin: @indent__m 0; - } - .product-reviews-summary { - .rating-summary { - margin: 0 4px 0 0; + &-description { + margin: @indent__m 0; } - .reviews-actions { - margin-top: 5px; - text-transform: lowercase; - font-size: @font-size__s; + .product-reviews-summary { + .rating-summary { + margin: 0 4px 0 0; + } + .reviews-actions { + font-size: @font-size__s; + margin-top: 5px; + text-transform: lowercase; + } } - } - .price-box { - margin: @indent__s 0 @indent__m; - .price { - font-weight: bold; - } - .price-label { - font-size: @font-size__s; - .lib-css(color, @text__color__muted); + .price-box { + margin: @indent__s 0 @indent__m; + .price { + font-weight: bold; + } + .price-label { + .lib-css(color, @text__color__muted); + font-size: @font-size__s; + } } - } - .old-price { - margin: @indent__xs 0; - .price { - font-weight: normal; + .old-price { + margin: @indent__xs 0; + .price { + font-weight: normal; + } } - } - .minimal-price { - .price-container { - display: block; + .minimal-price { + .price-container { + display: block; + } } - } - .minimal-price-link { - margin-top: @indent__xs; - } + .minimal-price-link { + margin-top: @indent__xs; + } - .price-from, - .price-to { - margin: 0; - } + .price-from, + .price-to { + margin: 0; + } - .tocompare { - .lib-icon-font-symbol( + .tocompare { + .lib-icon-font-symbol( @icon-compare-full - ); - } - .tocart { - white-space: nowrap; - border-radius: 0; - .lib-font-size(13px); - line-height: 1; - padding-top: @indent__s; - padding-bottom: @indent__s; + ); + } + .tocart { + .lib-font-size(13px); + border-radius: 0; + line-height: 1; + padding-bottom: @indent__s; + padding-top: @indent__s; + white-space: nowrap; + } } } -} -.column.main { - .product { - &-items { - margin-left: -20px; - } - &-item { - padding-left: 20px; + .column.main { + .product { + &-items { + margin-left: -20px; + } + &-item { + padding-left: 20px; + } } } -} - -.price-container { - .price { - .lib-font-size(14); - } - .price-including-tax + .price-excluding-tax, - .weee { - margin-top: 5px; - } - - .price-including-tax + .price-excluding-tax, - .weee, - .price-including-tax + .price-excluding-tax .price, - .weee .price, - .weee + .price-excluding-tax:before, - .weee + .price-excluding-tax .price { - .lib-font-size(11); - } + .price-container { + .price { + .lib-font-size(14); + } - .weee { - &:before { - content: "("attr(data-label) ": "; + .price-including-tax + .price-excluding-tax, + .weee { + margin-top: 5px; } - &:after { - content: ")"; + + .price-including-tax + .price-excluding-tax, + .weee, + .price-including-tax + .price-excluding-tax .price, + .weee .price, + .weee + .price-excluding-tax:before, + .weee + .price-excluding-tax .price { + .lib-font-size(11); } - + .price-excluding-tax { + + .weee { &:before { - content: attr(data-label) ": "; + content: '(' attr(data-label) ': '; + } + &:after { + content: ')'; + } + + .price-excluding-tax { + &:before { + content: attr(data-label) ': '; + } } } } -} -.products-list { - .product { - &-item { - display: table; - width: 100%; + .products-list { + .product { + &-item { + display: table; + width: 100%; - &-info { - display: table-row; - } - &-photo { - width: 1%; - padding: 0 @indent__l @indent__l 0; - vertical-align: top; - display: table-cell; - } - &-details { - vertical-align: top; - display: table-cell; + &-info { + display: table-row; + } + + &-photo { + display: table-cell; + padding: 0 @indent__l @indent__l 0; + vertical-align: top; + width: 1%; + } + + &-details { + display: table-cell; + vertical-align: top; + } } } - } - .product-image-wrapper { - &:extend(.abs-reset-image-wrapper all); - } -} + .product-image-wrapper { + &:extend(.abs-reset-image-wrapper all); + } + } } // -// Mobile -//-------------------------------------- +// Mobile +// _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) { .products-list .product { @@ -231,14 +232,16 @@ .products-grid & { &:hover, &.active { - margin: -10px; - padding: 9px; - border: 1px solid @color-gray-light2; @_shadow: 3px 3px 4px 0 rgba(0, 0, 0, .3); + + .lib-css(background, @color-white); .lib-css(box-shadow, @_shadow); + border: 1px solid @color-gray-light2; + margin: -10px; + padding: 9px; position: relative; z-index: 2; - .lib-css(background, @color-white); + .product-item-inner { display: block; } @@ -247,17 +250,18 @@ } .product-item-inner { .products-grid & { - position: absolute; - left: 0; - right: -1px; - z-index: 2; + @_shadow: 3px 4px 4px 0 rgba(0, 0, 0, .3); + .lib-css(background, @color-white); - padding: 0 9px 9px 9px; - margin: 9px 0 0 -1px; + .lib-css(box-shadow, @_shadow); border: 1px solid @color-gray-light2; border-top: none; - @_shadow: 3px 4px 4px 0 rgba(0, 0, 0, .3); - .lib-css(box-shadow, @_shadow); + left: 0; + margin: 9px 0 0 -1px; + padding: 0 9px 9px 9px; + position: absolute; + right: -1px; + z-index: 2; } } @@ -268,8 +272,8 @@ } .actions-primary + .actions-secondary { display: table-cell; - width: 50%; padding-left: 10px; + width: 50%; > .action { margin-right: 10px; &:last-child { @@ -277,7 +281,9 @@ } } } - .actions-primary { display: table-cell; } + .actions-primary { + display: table-cell; + } } .products-grid { @@ -307,9 +313,8 @@ } // -// -// Desktop -//-------------------------------------- +// Desktop +// _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .page-products .products-grid .product-item { width: 100%/3 } @@ -322,9 +327,9 @@ .page-layout-3columns .products-grid .product-item { width: 100%/4 } .page-products .products-grid .product-items { margin: 0; } .page-products .products-grid .product-item { - width: 24.439%; margin-left: calc(~"(100% - 4 * 24.439%) / 3"); padding: 0; + width: 24.439%; &:nth-child(4n+1) { margin-left: 0; } From 106385057e5f5c81b9dc90ce7feae185cdc0af7f Mon Sep 17 00:00:00 2001 From: Olga Matviienko Date: Wed, 18 Nov 2015 13:44:54 +0200 Subject: [PATCH 10/79] MAGETWO-45095: Product base images aren't centered in Wish List on Storefront --- .../web/css/source/module/_listings.less | 126 ++++++++++++------ .../web/css/source/_module.less | 1 - .../web/css/source/module/_listings.less | 122 ++++++++++++++--- .../web/css/source/_module.less | 7 +- 4 files changed, 185 insertions(+), 71 deletions(-) diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less index 018a0afdf2ed1..631d52e144ac4 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less @@ -291,24 +291,16 @@ width: 100%/3; } - .page-layout-1column .products-grid .product-item { - width: 100%/3; - } - - .page-layout-3columns .products-grid .product-item { - width: 100%/3; - } - - .page-products .products-grid .product-item { - width: 100%/3; - } - - .page-products.page-layout-1column .products-grid .product-item { - width: 100%/3; - } - - .page-products.page-layout-3columns .products-grid .product-item { - width: 100%/3; + .page-products, + .page-layout-1column, + .page-layout-3columns, + .page-products.page-layout-1column, + .page-products.page-layout-3columns { + .products-grid { + .product-item { + width: 100%/3; + } + } } } @@ -317,49 +309,95 @@ // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { - .page-products .products-grid .product-item { - width: 100%/3; + .page-products { + .products-grid { + .product-item { + width: 100%/3; + } + } } - .page-products.page-layout-1column .products-grid .product-item { - width: 100%/4; + .page-products.page-layout-1column { + .products-grid { + .product-item { + width: 100%/4; + } + } } - .page-products.page-layout-3columns .products-grid .product-item { - width: 100%/2; + .page-products.page-layout-3columns { + .products-grid { + .product-item { + width: 100%/2; + } + } } } .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) { - .products-grid .product-item { - width: 100%/5; + .products-grid { + .product-item { + width: 100%/5; + } } - .page-layout-1column .products-grid .product-item { - width: 100%/6; + .page-layout-1column { + .products-grid { + .product-item { + width: 100%/6; + } + } } - .page-layout-3columns .products-grid .product-item { - width: 100%/4; + .page-layout-3columns { + .products-grid { + .product-item { + width: 100%/4; + } + } } - .page-products .products-grid .product-items { - margin: 0; - } + .page-products { + .products-grid { + .product-items { + margin: 0; + } + .product-item { + margin-left: calc(~"(100% - 4 * 23.233%) / 3"); + padding: 0; + width: 23.233%; - .page-products .products-grid .product-item { - margin-left: calc(~"(100% - 4 * 23.233%) / 3"); - padding: 0; - width: 23.233%; - &:nth-child(4n+1) { - margin-left: 0; + &:nth-child(4n+1) { + margin-left: 0; + } + } } } - .page-products.page-layout-1column .products-grid .product-item { - width: 100%/5; - } + .page-products { + &.page-layout-1column { + .products-grid { + .product-item { + margin-left: 0; + width: 100%/5; + } + } + } + + &.page-layout-3columns { + .products-grid { + .product-item { + margin-left: 1%; + width: 32.667%; - .page-products.page-layout-3columns .products-grid .product-item { - width: 100%/4; + &:nth-child(3n) { + margin-left: 1%; + } + + &:nth-child(3n+1) { + margin-left: 0; + } + } + } + } } } diff --git a/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less index 49720b30a2cf6..b279d55a41e8a 100644 --- a/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less @@ -231,7 +231,6 @@ .products-grid { .product-item { margin-bottom: @indent__base; - width: 50%; } .product-item-actions { margin: 0; diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 55b61309bf5b4..f9b9ad930a493 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -300,16 +300,22 @@ .products-grid { .product-item { - width: 100%/3; margin-bottom: @indent__base; + width: 100%/3; } } - .page-layout-1column .products-grid .product-item { width: 100%/3 } - .page-layout-3columns .products-grid .product-item { width: 100%/3 } - .page-products .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/3 } + .page-products, + .page-layout-1column, + .page-layout-3columns, + .page-products.page-layout-1column, + .page-products.page-layout-3columns { + .products-grid { + .product-item { + width: 100%/3; + } + } + } } // @@ -317,23 +323,97 @@ // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { - .page-products .products-grid .product-item { width: 100%/3 } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/4 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/2 } + .page-products { + .products-grid { + .product-item { + width: 100%/3; + } + } + } + + .page-products.page-layout-1column { + .products-grid { + .product-item { + width: 100%/4; + } + } + } + + .page-products.page-layout-3columns { + .products-grid { + .product-item { + width: 100%/2; + } + } + } } + .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) { - .products-grid .product-item { width: 100%/5 } - .page-layout-1column .products-grid .product-item { width: 100%/6 } - .page-layout-3columns .products-grid .product-item { width: 100%/4 } - .page-products .products-grid .product-items { margin: 0; } - .page-products .products-grid .product-item { - margin-left: calc(~"(100% - 4 * 24.439%) / 3"); - padding: 0; - width: 24.439%; - &:nth-child(4n+1) { - margin-left: 0; + + .products-grid { + .product-item { + width: 100%/5; + } + } + + .page-layout-1column { + .products-grid { + .product-item { + width: 100%/6; + } + } + } + + .page-layout-3columns { + .products-grid { + .product-item { + width: 100%/4; + } + } + } + + .page-products { + .products-grid { + .product-items { + margin: 0; + } + .product-item { + margin-left: calc(~"(100% - 4 * 24.439%) / 3"); + padding: 0; + width: 24.439%; + + &:nth-child(4n+1) { + margin-left: 0; + } + } + } + } + + .page-products { + &.page-layout-1column { + .products-grid { + .product-item { + margin-left: 0; + width: 100%/5; + } + } + } + + &.page-layout-3columns { + .products-grid { + .product-item { + margin-left: 1%; + width: 32.667%; + + &:nth-child(3n) { + margin-left: 1%; + } + + &:nth-child(3n+1) { + margin-left: 0; + } + } + } } } - .page-products.page-layout-1column .products-grid .product-item { width: 100%/5 } - .page-products.page-layout-3columns .products-grid .product-item { width: 100%/4 } } diff --git a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less index 595ba043af612..2298b56791497 100644 --- a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less @@ -244,10 +244,6 @@ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__s) { .wishlist-index-index { .products-grid { - .product-item { - margin-bottom: 20px; - width: 50%; - } .product-item-actions { margin: 0; } @@ -344,9 +340,10 @@ } .product-item { margin-bottom: @indent__base; - margin-left: calc(~"(100% - 4 * 24%) / 3"); + margin-left: calc(~"(100% - 4 * 24.439%) / 3"); padding: 0; width: 24.439%; + &:nth-child(4n+1) { margin-left: 0; } From a0ac37f0349320f4c36328d4ad44bc2cf946aa77 Mon Sep 17 00:00:00 2001 From: Sergey Semenov Date: Wed, 18 Nov 2015 18:35:48 +0200 Subject: [PATCH 11/79] MAGETWO-45027: CSRF on Removing Item from Wishlist --- .../testsuite/Magento/Wishlist/Controller/IndexTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php index 1f17a766c424f..8a86f51af9241 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php @@ -83,6 +83,12 @@ public function testItemColumnBlock() */ public function testAddActionProductNameXss() { + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get('Magento\Framework\Data\Form\FormKey'); + $this->getRequest()->setPostValue([ + 'form_key' => $formKey->getFormKey(), + ]); + $this->dispatch('wishlist/index/add/product/1?nocookie=1'); $messages = $this->_messages->getMessages()->getItems(); $isProductNamePresent = false; From 5f5f0bb38b948a92e7983cbc4f78a39a7bcb3adf Mon Sep 17 00:00:00 2001 From: Maxim Medinskiy Date: Wed, 18 Nov 2015 18:44:29 +0200 Subject: [PATCH 12/79] MAGETWO-45732: [CMS] Remove old code --- .../Unit/Controller/Adminhtml/Page/InlineEditTest.php | 9 --------- .../tests/app/Magento/Cms/Test/Fixture/CmsPage.xml | 1 - 2 files changed, 10 deletions(-) diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php index 8b1b9de7c84e0..8f26e20758656 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/InlineEditTest.php @@ -250,8 +250,6 @@ public function testSetCmsPageData() 'is_active' => '1', 'sort_order' => '1', 'custom_theme' => '3', - 'website_root' => '1', - 'under_version_control' => '0', 'store_id' => ['0'] ]; $pageData = [ @@ -261,7 +259,6 @@ public function testSetCmsPageData() 'identifier' => 'home', 'is_active' => '1', 'custom_theme' => '3', - 'under_version_control' => '0', ]; $getData = [ 'page_id' => '2', @@ -274,9 +271,6 @@ public function testSetCmsPageData() 'sort_order' => '1', 'custom_theme' => '3', 'custom_root_template' => '1column', - 'published_revision_id' => '0', - 'website_root' => '1', - 'under_version_control' => '0', 'store_id' => ['0'] ]; $mergedData = [ @@ -290,9 +284,6 @@ public function testSetCmsPageData() 'sort_order' => '1', 'custom_theme' => '3', 'custom_root_template' => '1column', - 'published_revision_id' => '0', - 'website_root' => '1', - 'under_version_control' => '0', 'store_id' => ['0'] ]; $this->cmsPage->expects($this->once())->method('getData')->willReturn($getData); diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Fixture/CmsPage.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Fixture/CmsPage.xml index 3f2d47608055f..bfc521b339479 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Fixture/CmsPage.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Fixture/CmsPage.xml @@ -33,7 +33,6 @@ - From 48d9643a0188807cca645e969c7752e60a7b174f Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk Date: Fri, 20 Nov 2015 10:48:42 +0200 Subject: [PATCH 13/79] MAGETWO-44131: Sometimes "Estimate Shipping & Tax" section overlaps Newsletter Subscription form in Shopping Cart --- .../view/frontend/web/js/view/cart/totals.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js index c47f9e20ddb29..62688826376e3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js @@ -6,14 +6,24 @@ define( [ 'uiComponent', - 'Magento_Checkout/js/model/totals' + 'Magento_Checkout/js/model/totals', + 'Magento_Checkout/js/model/shipping-service' ], - function (Component, totalsService) { + function (Component, totalsService, shippingService) { 'use strict'; return Component.extend({ - isLoading: totalsService.isLoading + isLoading: totalsService.isLoading, + initialize: function () { + this._super(); + totalsService.totals.subscribe(function() { + window.dispatchEvent(new Event('resize')); + }); + shippingService.getShippingRates().subscribe(function() { + window.dispatchEvent(new Event('resize')); + }); + } }); } ); From 7d610f0b3b17ceb100b72fd6a89120d4c82b1a3a Mon Sep 17 00:00:00 2001 From: Sergey Semenov Date: Fri, 20 Nov 2015 13:19:42 +0200 Subject: [PATCH 14/79] MAGETWO-45027: CSRF on Removing Item from Wishlist --- .../Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php index 0eedeef274490..8f0d5333965b4 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php @@ -117,6 +117,9 @@ protected function initCookies() */ public function write($url, $params = [], $method = CurlInterface::POST, $headers = []) { + if ($this->formKey) { + $params['form_key'] = $this->formKey; + } $headers = ['Set-Cookie:' . $this->cookies]; $this->transport->write($url, http_build_query($params), $method, $headers); } From 9d58f12023ed65cb7cbb26bdf27517a8fe9bccdf Mon Sep 17 00:00:00 2001 From: Evgeniy Kolesov Date: Thu, 26 Nov 2015 17:25:45 +0200 Subject: [PATCH 15/79] MAGETWO-43412: [UI] Create collapsible component for forms --- .../base/web/js/form/components/fieldset.js | 75 ++++++++- .../base/web/templates/form/fieldset.html | 42 +++++- .../module/main/_collapsible-blocks.less | 58 +++++-- .../web/css/source/module/main/_page-nav.less | 142 ++++++++++++------ .../web/css/source/_module.less | 6 +- 5 files changed, 248 insertions(+), 75 deletions(-) diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js index 2369c3740f468..543b8921248fa 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js @@ -11,7 +11,80 @@ define([ defaults: { template: 'ui/form/fieldset', collapsible: false, - opened: true + changed: false, + loading: false, + error: false, + opened: false + }, + /** + * Extends instance with defaults. Invokes parent initialize method. + * Calls initListeners and pushParams methods. + */ + initialize: function () { + _.bindAll(this, 'onChildrenUpdate', 'onChildrenError', 'onContentLoading'); + + return this._super(); + }, + + /** + * Calls initObservable of parent class. + * Defines observable properties of instance. + * @return {Object} - reference to instance + */ + initObservable: function () { + this._super() + .observe('changed loading error'); + + return this; + }, + + /** + * Calls parent's initElement method. + * Assignes callbacks on various events of incoming element. + * @param {Object} elem + * @return {Object} - reference to instance + */ + initElement: function (elem) { + this._super(); + + elem.on({ + 'update': this.onChildrenUpdate, + 'loading': this.onContentLoading, + 'error': this.onChildrenError + }); + + return this; + }, + + /** + * Is being invoked on children update. + * Sets changed property to one incoming. + * + * @param {Boolean} hasChanged + */ + onChildrenUpdate: function (hasChanged) { + if (!hasChanged) { + hasChanged = _.some(this.delegate('hasChanged')); + } + + this.changed(hasChanged); + }, + + /** + * Is being invoked on children validation error. + * Sets error property to one incoming. + */ + onChildrenError: function () { + var hasErrors = this.elems.some('error'); + + this.error(hasErrors); + }, + + /** + * Callback that sets loading property to true. + */ + onContentLoading: function (isLoading) { + this.loading(isLoading); } }); }); diff --git a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html index ac6e00ae67b3c..71b6fc91c3244 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html @@ -4,19 +4,45 @@ * See COPYING.txt for license details. */ --> - -
-
+
+ data-bind="click: toggleOpened, keyboard: { 13: toggleOpened }"> + + + + + + + + + + + + + + + + + +
-
+
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less index e79b59ff8890f..97602ba06a235 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less @@ -18,7 +18,7 @@ @collapsible-title__hover__color: darken(@collapsible-title__color, 15%); @collapsible-title__disabled__color: lighten(@collapsible-title__color, 30%); -@collapsible-title-icon__size: @collapsible-title__font-size; +@collapsible-title-icon__size: 1.8rem; @collapsible-title-support-text__font-size: 1.4rem; @collapsible-sub-block__indent: 4rem; @@ -29,20 +29,23 @@ // Mixins // _____________________________________________ -.admin__collapsible-block-wrapper() { +.__collapsible-block-wrapper-pattern() { border-bottom: 1px solid @collapsible__border-color; + .admin__fieldset > &:last-child { + border-bottom-width: 0; + } } -.admin__collapsible-sub-block-wrapper() { +.__collapsible-sub-block-wrapper-pattern() { margin-left: @collapsible-sub-block__indent; } -.admin__collapsible-content() { +.__collapsible-content-pattern() { border: 0; padding: 0 0 @collapsible-content__indent; } -.admin__collapsible-title() { +.__collapsible-title-pattern() { clear: both; color: @collapsible-title__color; cursor: pointer; @@ -82,9 +85,15 @@ right: 1.3rem; top: 2.3rem; } + + &._loading { + &:before { + content: ''; + } + } } -.admin__collapsible-sub-title() { +.__collapsible-sub-title-pattern() { padding-left: (@collapsible-title-icon__size + 1rem); padding-right: 0; @@ -94,7 +103,7 @@ } } -.admin__collapsible-text() { +.__collapsible-text-pattern() { margin-bottom: .5em; margin-top: 1rem; } @@ -102,9 +111,9 @@ // .admin__collapsible-block-wrapper { - .admin__collapsible-block-wrapper(); + .__collapsible-block-wrapper-pattern(); .admin__collapsible-title { - .admin__collapsible-title(); + .__collapsible-title-pattern(); } &.opened, @@ -145,10 +154,24 @@ border-bottom: 0; } } +.admin__collapsible-content { + max-height: 0; + transform-origin: 0 0; + transform: scaleY(0); + transition: transform 100ms ease-in-out, max-height 100ms ease-in-out, visibility 100ms ease-in-out; + visibility: hidden; + + &._show { + max-height: 100%; + transform: scaleY(1); + visibility: visible; + } +} .fieldset-wrapper { .admin__collapsible-block-wrapper { .fieldset-wrapper-title { + .lib-css(user-select, none, 1); .actions { position: absolute; right: 1.3rem; @@ -167,7 +190,14 @@ &:before { left: 0; right: auto; - top: 2.2rem; + top: 2rem; + } + + &._loading { + .admin__page-nav-item-message-loader { + left: 0; + right: auto; + } } } } @@ -180,7 +210,7 @@ .admin__collapsible-block { .comment { // ToDo UI: rename to .collapsible-text - .admin__collapsible-text(); + .__collapsible-text-pattern(); } // Second level @@ -189,21 +219,21 @@ .entry-edit-head { > a { - .admin__collapsible-sub-title(); + .__collapsible-sub-title-pattern(); } } } ~ .admin__collapsible-block { // ToDo UI: rename to .collapsible-content - .admin__collapsible-content(); + .__collapsible-content-pattern(); } } .section-config > .admin__collapsible-block > a, .accordion > dt a, .accordion .admin__collapsible-block > a { - .admin__collapsible-title(); + .__collapsible-title-pattern(); > i { // ToDo UI: change to .collapsible-sub-title diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less index 4c3d40891c9aa..138868bd71474 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less @@ -205,74 +205,118 @@ padding: @admin__page-nav-link__padding; transition: @admin__page-nav-transition; word-wrap: break-word; +} - &._changed { - .admin__page-nav-item-message { - &._changed { - display: inline-block; - } - } - } +// Messages in tooltip - &._error { - .admin__page-nav-item-message { - &._error { - display: inline-block; - } +.admin__page-nav-item-messages { + display: inline-block; + + .admin__page-nav-item-message-tooltip { + background: @admin__page-nav-tooltip__background; + border-radius: 1px; + border: 1px solid @admin__page-nav-tooltip__background; + bottom: 3.7rem; + box-shadow: @admin__page-nav-tooltip__box-shadow; + display: none; + font-size: @font-size__base; + font-weight: @font-weight__regular; + left: -@indent__s; + line-height: @line-height__base; + padding: 1.5rem; + position: absolute; + text-transform: none; + width: 27rem; + word-break: normal; + z-index: 2; + &:after, + &:before { + .lib-arrow( + @_position: down, + @_size: 15px, + @_color: @admin__page-nav-tooltip__background + ); + content: ''; + display: block; + left: 2rem; + position: absolute; + top: 100%; + z-index: 3; + } + &:after { + border-top-color: @admin__page-nav-tooltip__background; + margin-top: -1px; + z-index: 4; + } + &:before { + border-top-color: @admin__page-nav-tooltip__border-color; + margin-top: 1px; } } } -// Messages in tooltip +.admin__page-nav-item-message-loader { + display: none; + margin-top: -(@admin__page-nav-item-message-loader__font-size/2); + position: absolute; + right: 0; + top: 50%; -.admin__page-nav-item-messages { - display: inline-block; + .spinner { + font-size: @admin__page-nav-item-message-loader__font-size; + margin-right: 1.5rem; + } - .admin__page-nav-item-message { - position: relative; + ._loading > .admin__page-nav-item-messages & { + display: inline-block; + } +} - &:hover { - z-index: @admin__page-nav-tooltip__z-index; +.admin__page-nav-item-message { + position: relative; - .admin__page-nav-item-message-tooltip { - display: block; - } - } + &:hover { + z-index: @admin__page-nav-tooltip__z-index; - &._error, - &._changed { - display: none; + .admin__page-nav-item-message-tooltip { + display: block; + } + } - .admin__page-nav-item-message-icon { - &:extend(.abs-icon all); - display: inline-block; - font-size: @admin__page-nav-title__font-size; - padding-left: .8em; - vertical-align: top; + &._error, + &._changed { + display: none; + } - &:after { - color: @admin__page-nav-link__changed__color; - content: @admin__page-nav-icon-changed__content; - } + .admin__page-nav-item-message-icon { + &:extend(.abs-icon all); + display: inline-block; + font-size: @admin__page-nav-title__font-size; + padding-left: .8em; + vertical-align: baseline; + &:after { + color: @admin__page-nav-link__changed__color; + content: @admin__page-nav-icon-changed__content; } } + } - &._error { - .admin__page-nav-item-message-icon { - &:after { - color: @admin__page-nav-icon-error__color; - content: @admin__page-nav-icon-error__content; - } - } + &._changed { + ._changed:not(._error) > .admin__page-nav-item-messages & { + display: inline-block; } } - .admin__page-nav-item-message-loader { - display: none; - margin-top: -(@admin__page-nav-item-message-loader__font-size/2); - position: absolute; - right: 0; - top: 50%; + &._error { + .admin__page-nav-item-message-icon { + &:after { + color: @admin__page-nav-icon-error__color; + content: @admin__page-nav-icon-error__content; + } + } + + ._error > .admin__page-nav-item-messages & { + display: inline-block; .spinner { font-size: @admin__page-nav-item-message-loader__font-size; diff --git a/app/design/adminhtml/Magento/backend/Magento_Config/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Config/web/css/source/_module.less index 3ab522c3425d4..08feb23d73b43 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Config/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Config/web/css/source/_module.less @@ -8,7 +8,7 @@ // _____________________________________________ .section-config { - .admin__collapsible-block-wrapper(); + .__collapsible-block-wrapper-pattern(); .admin__collapsible-block { tr { @@ -29,10 +29,10 @@ // Sub blocks .section-config { - .admin__collapsible-sub-block-wrapper(); + .__collapsible-sub-block-wrapper-pattern(); .entry-edit-head { > a { - .admin__collapsible-sub-title(); + .__collapsible-sub-title-pattern(); } } } From 1bc4f34a7270293511807e3aac122c0d59365390 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 27 Nov 2015 11:26:21 +0200 Subject: [PATCH 16/79] MAGETWO-45887: Persistent XSS on Create User Account --- .../Magento/Framework/View/Page/Config/Renderer.php | 2 +- .../Framework/View/Test/Unit/Page/Config/RendererTest.php | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index 660d1d9c5aa6b..c117472325c06 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -106,7 +106,7 @@ public function renderHeadContent() */ public function renderTitle() { - return '' . $this->pageConfig->getTitle()->get() . '' . "\n"; + return '' . $this->escaper->escapeHtml($this->pageConfig->getTitle()->get()) . '' . "\n"; } /** diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php index 62c52b45cc818..8164bd3bb18a2 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php @@ -179,11 +179,15 @@ public function testRenderTitle() $this->pageConfigMock->expects($this->any()) ->method('getTitle') - ->will($this->returnValue($this->titleMock)); + ->willReturn($this->titleMock); $this->titleMock->expects($this->once()) ->method('get') - ->will($this->returnValue($title)); + ->willReturn($title); + + $this->escaperMock->expects($this->once()) + ->method('escapeHtml') + ->willReturnArgument(0); $this->assertEquals($expected, $this->renderer->renderTitle()); } From edac73c7a1c8c59a5de86e396e541e9b0464e2bf Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk Date: Fri, 27 Nov 2015 12:11:29 +0200 Subject: [PATCH 17/79] MAGETWO-44131: Sometimes "Estimate Shipping & Tax" section overlaps Newsletter Subscription form in Shopping Cart --- .../Checkout/view/frontend/web/js/view/cart/totals.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js index 62688826376e3..6ea1a78315499 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js @@ -2,7 +2,6 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -/*global define*/ define( [ 'uiComponent', @@ -15,12 +14,16 @@ define( return Component.extend({ isLoading: totalsService.isLoading, + + /** + * @override + */ initialize: function () { this._super(); - totalsService.totals.subscribe(function() { + totalsService.totals.subscribe(function () { window.dispatchEvent(new Event('resize')); }); - shippingService.getShippingRates().subscribe(function() { + shippingService.getShippingRates().subscribe(function () { window.dispatchEvent(new Event('resize')); }); } From be5eb50fae3a1393bfc8afa31e5199ba01fc7a3f Mon Sep 17 00:00:00 2001 From: Evgeniy Kolesov Date: Fri, 27 Nov 2015 17:11:20 +0200 Subject: [PATCH 18/79] MAGETWO-43412: [UI] Create collapsible component for forms --- app/code/Magento/Ui/view/base/web/templates/form/fieldset.html | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html index 71b6fc91c3244..e822098b248b3 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html @@ -18,7 +18,6 @@ '_error': error }"> data-bind="click: toggleOpened, keyboard: { 13: toggleOpened }"> - From 0cae85046bb4880ddcefa3c2e9ea1183e0ef5345 Mon Sep 17 00:00:00 2001 From: Ievgen Sentiabov Date: Fri, 27 Nov 2015 18:23:25 +0200 Subject: [PATCH 19/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Added new implementation of Braintree payment solution --- .../Block/Adminhtml/Form/Field/Cctypes.php | 64 ++++ .../Block/Adminhtml/Form/Field/Countries.php | 59 ++++ .../Form/Field/CountryCreditCard.php | 111 +++++++ app/code/Magento/BraintreeTwo/Block/Form.php | 80 +++++ app/code/Magento/BraintreeTwo/Block/Info.php | 47 +++ .../Magento/BraintreeTwo/Block/Payment.php | 56 ++++ .../Controller/Token/GetClientToken.php | 75 +++++ .../BraintreeTwo/Gateway/Config/Config.php | 163 ++++++++++ .../Gateway/Helper/SubjectReader.php | 28 ++ .../Gateway/Http/Client/TransactionSale.php | 66 ++++ .../Gateway/Http/TransferFactory.php | 41 +++ .../Gateway/Request/AddressDataBuilder.php | 119 +++++++ .../Gateway/Request/CustomerDataBuilder.php | 67 ++++ .../Gateway/Request/PaymentDataBuilder.php | 80 +++++ .../Gateway/Request/SettlementDataBuilder.php | 28 ++ .../Gateway/Response/CardDetailsHandler.php | 85 +++++ .../Response/PaymentDetailsHandler.php | 72 +++++ .../Gateway/Validator/ResponseValidator.php | 63 ++++ .../Magento/BraintreeTwo/Helper/CcType.php | 48 +++ .../Magento/BraintreeTwo/Helper/Country.php | 50 +++ app/code/Magento/BraintreeTwo/LICENSE.txt | 48 +++ app/code/Magento/BraintreeTwo/LICENSE_AFL.txt | 48 +++ .../Model/Adapter/BraintreeClientToken.php | 31 ++ .../Model/Adapter/BraintreeConfiguration.php | 52 +++ .../Model/Adapter/BraintreeCreditCard.php | 36 +++ .../Model/Adapter/BraintreeCustomer.php | 44 +++ .../Model/Adapter/BraintreePaymentMethod.php | 47 +++ .../Model/Adapter/BraintreeTransaction.php | 75 +++++ .../Model/Adminhtml/Source/CcType.php | 35 ++ .../Model/Adminhtml/Source/Environment.php | 36 +++ .../Model/Adminhtml/Source/PaymentAction.php | 34 ++ .../Model/Adminhtml/System/Config/Country.php | 76 +++++ .../System/Config/CountryCreditCard.php | 108 +++++++ .../BraintreeTwo/Model/Ui/ConfigProvider.php | 53 ++++ .../Observer/DataAssignObserver.php | 34 ++ app/code/Magento/BraintreeTwo/README.md | 1 + .../BraintreeTwo/Test/Unit/Block/FormTest.php | 146 +++++++++ .../Test/Unit/Gateway/Config/ConfigTest.php | 285 +++++++++++++++++ .../Http/Client/TransactionSaleTest.php | 142 +++++++++ .../Unit/Gateway/Http/TransferFactoryTest.php | 57 ++++ .../Request/AddressDataBuilderTest.php | 182 +++++++++++ .../Request/CustomerDataBuilderTest.php | 128 ++++++++ .../Request/PaymentDataBuilderTest.php | 109 +++++++ .../Request/SettlementDataBuilderTest.php | 23 ++ .../Response/CardDetailsHandlerTest.php | 152 +++++++++ .../Response/PaymentDetailsHandlerTest.php | 124 ++++++++ .../Validator/ResponseValidatorTest.php | 142 +++++++++ .../Test/Unit/Helper/CcTypeTest.php | 64 ++++ .../Test/Unit/Helper/CountryTest.php | 94 ++++++ .../System/Config/CountryCreditCardTest.php | 190 +++++++++++ .../Adminhtml/System/Config/CountryTest.php | 148 +++++++++ .../Test/Unit/Model/Ui/ConfigProviderTest.php | 89 ++++++ .../Unit/Observer/DataAssignObserverTest.php | 60 ++++ app/code/Magento/BraintreeTwo/composer.json | 29 ++ .../Magento/BraintreeTwo/etc/adminhtml/di.xml | 21 ++ .../BraintreeTwo/etc/adminhtml/system.xml | 117 +++++++ app/code/Magento/BraintreeTwo/etc/config.xml | 37 +++ app/code/Magento/BraintreeTwo/etc/di.xml | 128 ++++++++ app/code/Magento/BraintreeTwo/etc/events.xml | 13 + .../Magento/BraintreeTwo/etc/frontend/di.xml | 30 ++ .../BraintreeTwo/etc/frontend/routes.xml | 14 + app/code/Magento/BraintreeTwo/etc/module.xml | 15 + .../Magento/BraintreeTwo/registration.php | 10 + .../layout/sales_order_create_index.xml | 27 ++ ...order_create_load_block_billing_method.xml | 17 + .../view/adminhtml/templates/form/cc.phtml | 69 ++++ .../adminhtml/templates/payment/script.phtml | 28 ++ .../view/adminhtml/web/css/styles.css | 57 ++++ .../view/adminhtml/web/images/cards.png | Bin 0 -> 5879 bytes .../view/adminhtml/web/js/braintree.js | 300 ++++++++++++++++++ .../view/base/web/js/validator.js | 93 ++++++ .../frontend/layout/checkout_index_index.xml | 52 +++ .../view/frontend/web/css/styles.css | 98 ++++++ .../frontend/web/js/view/payment/braintree.js | 28 ++ .../view/payment/method-renderer/braintree.js | 279 ++++++++++++++++ .../frontend/web/template/payment/form.html | 123 +++++++ composer.json | 1 + 77 files changed, 5881 insertions(+) create mode 100644 app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Cctypes.php create mode 100644 app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Countries.php create mode 100644 app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php create mode 100644 app/code/Magento/BraintreeTwo/Block/Form.php create mode 100644 app/code/Magento/BraintreeTwo/Block/Info.php create mode 100644 app/code/Magento/BraintreeTwo/Block/Payment.php create mode 100644 app/code/Magento/BraintreeTwo/Controller/Token/GetClientToken.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Config/Config.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Helper/SubjectReader.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Http/TransferFactory.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Request/AddressDataBuilder.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Request/CustomerDataBuilder.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Request/PaymentDataBuilder.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Request/SettlementDataBuilder.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php create mode 100644 app/code/Magento/BraintreeTwo/Gateway/Validator/ResponseValidator.php create mode 100644 app/code/Magento/BraintreeTwo/Helper/CcType.php create mode 100644 app/code/Magento/BraintreeTwo/Helper/Country.php create mode 100644 app/code/Magento/BraintreeTwo/LICENSE.txt create mode 100644 app/code/Magento/BraintreeTwo/LICENSE_AFL.txt create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeClientToken.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeConfiguration.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeCreditCard.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeCustomer.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreePaymentMethod.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeTransaction.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/Environment.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/PaymentAction.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php create mode 100644 app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php create mode 100644 app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php create mode 100644 app/code/Magento/BraintreeTwo/README.md create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/TransferFactoryTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/AddressDataBuilderTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/SettlementDataBuilderTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Validator/ResponseValidatorTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Helper/CcTypeTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryCreditCardTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php create mode 100644 app/code/Magento/BraintreeTwo/Test/Unit/Observer/DataAssignObserverTest.php create mode 100644 app/code/Magento/BraintreeTwo/composer.json create mode 100644 app/code/Magento/BraintreeTwo/etc/adminhtml/di.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/config.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/di.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/events.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/frontend/di.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/frontend/routes.xml create mode 100644 app/code/Magento/BraintreeTwo/etc/module.xml create mode 100644 app/code/Magento/BraintreeTwo/registration.php create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/layout/sales_order_create_index.xml create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/templates/form/cc.phtml create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/templates/payment/script.phtml create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/web/css/styles.css create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/web/images/cards.png create mode 100644 app/code/Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js create mode 100644 app/code/Magento/BraintreeTwo/view/base/web/js/validator.js create mode 100644 app/code/Magento/BraintreeTwo/view/frontend/layout/checkout_index_index.xml create mode 100644 app/code/Magento/BraintreeTwo/view/frontend/web/css/styles.css create mode 100644 app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/braintree.js create mode 100644 app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/method-renderer/braintree.js create mode 100644 app/code/Magento/BraintreeTwo/view/frontend/web/template/payment/form.html diff --git a/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Cctypes.php b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Cctypes.php new file mode 100644 index 0000000000000..a4f1da1a7ba6c --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Cctypes.php @@ -0,0 +1,64 @@ +ccTypeHelper = $ccTypeHelper; + } + + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + if (!$this->getOptions()) { + $this->setOptions($this->ccTypeHelper->getCcTypes()); + } + $this->setClass('cc-type-select'); + $this->setExtraParams('multiple="multiple"'); + return parent::_toHtml(); + } + + /** + * Sets name for input element + * + * @param string $value + * @return $this + */ + public function setInputName($value) + { + return $this->setName($value . '[]'); + } +} diff --git a/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Countries.php b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Countries.php new file mode 100644 index 0000000000000..10aea6da09efe --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/Countries.php @@ -0,0 +1,59 @@ +countryHelper = $countryHelper; + } + + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + if (!$this->getOptions()) { + $this->setOptions($this->countryHelper->getCountries()); + } + return parent::_toHtml(); + } + + /** + * Sets name for input element + * + * @param string $value + * @return $this + */ + public function setInputName($value) + { + return $this->setName($value); + } +} diff --git a/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php new file mode 100644 index 0000000000000..795a01ca200de --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php @@ -0,0 +1,111 @@ +countryRenderer) { + $this->countryRenderer = $this->getLayout()->createBlock( + Countries::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + } + return $this->countryRenderer; + } + + /** + * Returns renderer for country element + * + * @return \Magento\BraintreeTwo\Block\Adminhtml\Form\Field\Cctypes + */ + protected function getCcTypesRenderer() + { + if (!$this->ccTypesRenderer) { + $this->ccTypesRenderer = $this->getLayout()->createBlock( + Cctypes::class, + '', + ['data' => ['is_render_to_js_template' => true]] + ); + } + return $this->ccTypesRenderer; + } + + /** + * Prepare to render + * @return void + */ + protected function _prepareToRender() + { + $this->addColumn( + 'country_id', + [ + 'label' => __('Country'), + 'renderer' => $this->getCountryRenderer(), + ] + ); + $this->addColumn( + 'cc_types', + [ + 'label' => __('Allowed Credit Card Types'), + 'renderer' => $this->getCcTypesRenderer(), + ] + ); + $this->_addAfter = false; + $this->_addButtonLabel = __('Add Rule'); + } + + /** + * Prepare existing row data object + * + * @param \Magento\Framework\DataObject $row + * @return void + */ + protected function _prepareArrayRow(DataObject $row) + { + $country = $row->getCountryId(); + $options = []; + if ($country) { + $options['option_' . $this->getCountryRenderer()->calcOptionHash($country)] + = 'selected="selected"'; + + $ccTypes = $row->getCcTypes(); + if (!is_array($ccTypes)) { + $ccTypes = [$ccTypes]; + } + foreach ($ccTypes as $cardType) { + $options['option_' . $this->getCcTypesRenderer()->calcOptionHash($cardType)] + = 'selected="selected"'; + } + } + $row->setData('option_extra_attrs', $options); + return; + } +} diff --git a/app/code/Magento/BraintreeTwo/Block/Form.php b/app/code/Magento/BraintreeTwo/Block/Form.php new file mode 100644 index 0000000000000..9a716f8a87a9f --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Block/Form.php @@ -0,0 +1,80 @@ +sessionQuote = $sessionQuote; + $this->gatewayConfig = $gatewayConfig; + } + + /** + * Get list of available card types of order billing address country + * @return array + */ + public function getCcAvailableTypes() + { + $types = $this->_paymentConfig->getCcTypes(); + + // get only available for Braintree card types + $configCardTypes = array_fill_keys($this->gatewayConfig->getCcAvailableCardTypes(), ''); + $filteredTypes = array_intersect_key($types, $configCardTypes); + + $countryId = $this->sessionQuote->getQuote()->getBillingAddress()->getCountryId(); + $availableTypes = $this->gatewayConfig->getCountryAvailableCardTypes($countryId); + // filter card types only if specific card types are set for country + if (!empty($availableTypes)) { + $availableTypes = array_fill_keys($availableTypes, ''); + $filteredTypes = array_intersect_key($filteredTypes, $availableTypes); + } + return $filteredTypes; + } + + /** + * Check if cvv validation is available + * @return boolean + */ + public function useCvv() + { + return $this->gatewayConfig->useCvv(); + } +} diff --git a/app/code/Magento/BraintreeTwo/Block/Info.php b/app/code/Magento/BraintreeTwo/Block/Info.php new file mode 100644 index 0000000000000..933150cb26564 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Block/Info.php @@ -0,0 +1,47 @@ +config = $config; + } + + /** + * @return string + */ + public function getPaymentConfig() + { + $payment = $this->config->getConfig()['payment']; + $config = $payment[$this->getCode()]; + $config['code'] = $this->getCode(); + return json_encode($config, JSON_UNESCAPED_SLASHES); + } + /** + * @return string + */ + public function getCode() + { + return ConfigProvider::CODE; + } +} diff --git a/app/code/Magento/BraintreeTwo/Controller/Token/GetClientToken.php b/app/code/Magento/BraintreeTwo/Controller/Token/GetClientToken.php new file mode 100644 index 0000000000000..d5291178887e3 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Controller/Token/GetClientToken.php @@ -0,0 +1,75 @@ +logger = $logger; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function execute() + { + $controllerResult = $this->resultFactory->create(ResultFactory::TYPE_JSON); + + try { + $controllerResult->setData(['client_token' => $this->config->getClientToken()]); + } catch (\Exception $e) { + $this->logger->critical($e); + return $this->getErrorResponse($controllerResult); + } + + return $controllerResult; + } + + /** + * @param ResultInterface $controllerResult + * @return ResultInterface + */ + private function getErrorResponse(ResultInterface $controllerResult) + { + $controllerResult->setHttpResponseCode(Exception::HTTP_BAD_REQUEST); + $controllerResult->setData(['message' => __('Sorry, but something went wrong')]); + + return $controllerResult; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php b/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php new file mode 100644 index 0000000000000..5790972b48afa --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php @@ -0,0 +1,163 @@ +braintreeConfiguration = $braintreeConfiguration; + $this->braintreeClientToken = $braintreeClientToken; + + if ($this->getValue(self::KEY_ACTIVE)) { + $this->initCredentials(); + } + } + + /** + * Initializes credentials. + * + * @return void + */ + public function initCredentials() + { + if ($this->getValue(self::KEY_ENVIRONMENT) == Environment::ENVIRONMENT_PRODUCTION) { + $this->braintreeConfiguration->environment(Environment::ENVIRONMENT_PRODUCTION); + } else { + $this->braintreeConfiguration->environment(Environment::ENVIRONMENT_SANDBOX); + } + $this->braintreeConfiguration->merchantId($this->getValue(self::KEY_MERCHANT_ID)); + $this->braintreeConfiguration->publicKey($this->getValue(self::KEY_PUBLIC_KEY)); + $this->braintreeConfiguration->privateKey($this->getValue(self::KEY_PRIVATE_KEY)); + } + + /** + * Generate a new client token if necessary + * + * @return string + */ + public function getClientToken() + { + if (empty($this->clientToken)) { + $this->clientToken = $this->braintreeClientToken->generate(); + } + + return $this->clientToken; + } + + /** + * Return the country specific card type config + * + * @return array + */ + public function getCountrySpecificCardTypeConfig() + { + $countriesCardTypes = unserialize($this->getValue(self::KEY_COUNTRY_CREDIT_CARD)); + + return is_array($countriesCardTypes) ? $countriesCardTypes : []; + } + + /** + * Retrieve available credit card types + * + * @return array + */ + public function getCcAvailableCardTypes() + { + $ccTypes = $this->getValue(self::KEY_CC_TYPES); + + return !empty($ccTypes) ? explode(',', $ccTypes) : []; + } + + /** + * Retrieve mapper between Magento and Braintree card types + * + * @return array + */ + public function getCctypesMapper() + { + $result = json_decode( + $this->getValue(self::KEY_CC_TYPES_BRAINTREE_MAPPER), + true + ); + + return is_array($result) ? $result : []; + } + + /** + * Get list of card types available for country + * @param string $country + * @return array + */ + public function getCountryAvailableCardTypes($country) + { + $types = $this->getCountrySpecificCardTypeConfig(); + return (!empty($types[$country])) ? $types[$country] : []; + } + + /** + * Check if cvv field is enabled + * @return boolean + */ + public function useCvv() + { + return (bool) $this->getValue(self::KEY_USE_CVV); + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Helper/SubjectReader.php b/app/code/Magento/BraintreeTwo/Gateway/Helper/SubjectReader.php new file mode 100644 index 0000000000000..77304c6cabbe4 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Helper/SubjectReader.php @@ -0,0 +1,28 @@ +logger = $logger; + $this->braintreeTransaction = $braintreeTransaction; + } + + /** + * @inheritdoc + */ + public function placeRequest(TransferInterface $transferObject) + { + $data = $transferObject->getBody(); + $log = [ + 'request' => $data, + 'client' => self::class + ]; + $response['object'] = []; + + try { + $response['object'] = $this->braintreeTransaction->sale($data); + } catch (\Exception $e) { + throw new ClientException(__($e->getMessage())); + } finally { + $log['response'] = json_decode(json_encode($response['object']), true); + $this->logger->debug($log); + } + + return $response; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Http/TransferFactory.php b/app/code/Magento/BraintreeTwo/Gateway/Http/TransferFactory.php new file mode 100644 index 0000000000000..cd21881d36df4 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Http/TransferFactory.php @@ -0,0 +1,41 @@ +transferBuilder = $transferBuilder; + } + + /** + * Builds gateway transfer object + * + * @param array $request + * @return TransferInterface + */ + public function create(array $request) + { + return $this->transferBuilder + ->setBody($request) + ->build(); + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Request/AddressDataBuilder.php b/app/code/Magento/BraintreeTwo/Gateway/Request/AddressDataBuilder.php new file mode 100644 index 0000000000000..feff663bb658d --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Request/AddressDataBuilder.php @@ -0,0 +1,119 @@ +getOrder(); + $result = []; + + $billingAddress = $order->getBillingAddress(); + if ($billingAddress) { + $result[self::BILLING_ADDRESS] = [ + self::FIRST_NAME => $billingAddress->getFirstname(), + self::LAST_NAME => $billingAddress->getLastname(), + self::COMPANY => $billingAddress->getCompany(), + self::STREET_ADDRESS => $billingAddress->getStreetLine1(), + self::EXTENDED_ADDRESS => $billingAddress->getStreetLine2(), + self::LOCALITY => $billingAddress->getCity(), + self::REGION => $billingAddress->getRegionCode(), + self::POSTAL_CODE => $billingAddress->getPostcode(), + self::COUNTRY_CODE => $billingAddress->getCountryId() + ]; + } + + $shippingAddress = $order->getShippingAddress(); + if ($shippingAddress) { + $result[self::SHIPPING_ADDRESS] = [ + self::FIRST_NAME => $shippingAddress->getFirstname(), + self::LAST_NAME => $shippingAddress->getLastname(), + self::COMPANY => $shippingAddress->getCompany(), + self::STREET_ADDRESS => $shippingAddress->getStreetLine1(), + self::EXTENDED_ADDRESS => $shippingAddress->getStreetLine2(), + self::LOCALITY => $shippingAddress->getCity(), + self::REGION => $shippingAddress->getRegionCode(), + self::POSTAL_CODE => $shippingAddress->getPostcode(), + self::COUNTRY_CODE => $shippingAddress->getCountryId() + ]; + } + + return $result; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Request/CustomerDataBuilder.php b/app/code/Magento/BraintreeTwo/Gateway/Request/CustomerDataBuilder.php new file mode 100644 index 0000000000000..2fff378d67c17 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Request/CustomerDataBuilder.php @@ -0,0 +1,67 @@ +getOrder(); + $billingAddress = $order->getBillingAddress(); + + return [ + self::CUSTOMER => [ + self::FIRST_NAME => $billingAddress->getFirstname(), + self::LAST_NAME => $billingAddress->getLastname(), + self::COMPANY => $billingAddress->getCompany(), + self::PHONE => $billingAddress->getTelephone(), + self::EMAIL => $billingAddress->getEmail(), + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Request/PaymentDataBuilder.php b/app/code/Magento/BraintreeTwo/Gateway/Request/PaymentDataBuilder.php new file mode 100644 index 0000000000000..c9d1a743b2e93 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Request/PaymentDataBuilder.php @@ -0,0 +1,80 @@ +config = $config; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject) + { + $paymentDO = SubjectReader::readPayment($buildSubject); + + /** @var \Magento\Sales\Model\Order\Payment $payment */ + $payment = $paymentDO->getPayment(); + + $result = [ + self::AMOUNT => sprintf('%.2F', SubjectReader::readAmount($buildSubject)), + self::PAYMENT_METHOD_NONCE => $payment->getAdditionalInformation( + DataAssignObserver::PAYMENT_METHOD_NONCE + ) + ]; + + $merchantAccountId = $this->config->getValue(Config::KEY_MERCHANT_ACCOUNT_ID); + if (!empty($merchantAccountId)) { + $result[self::MERCHANT_ACCOUNT_ID] = $merchantAccountId; + } + + return $result; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Request/SettlementDataBuilder.php b/app/code/Magento/BraintreeTwo/Gateway/Request/SettlementDataBuilder.php new file mode 100644 index 0000000000000..4def50e3893d0 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Request/SettlementDataBuilder.php @@ -0,0 +1,28 @@ + [ + self::SUBMIT_FOR_SETTLEMENT => true + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php new file mode 100644 index 0000000000000..bb69ed079668d --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php @@ -0,0 +1,85 @@ +config = $config; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response) + { + $details = SubjectReader::readPayment($handlingSubject); + /** @var \Braintree_Transaction $transaction */ + $transaction = $response['object']->transaction; + /** @var \Magento\Sales\Model\Order\Payment $payment */ + $payment = $details->getPayment(); + ContextHelper::assertOrderPayment($payment); + + $card = self::CARD_DETAILS; + $creditCard = $transaction->$card; + $payment->setCcLast4($creditCard[self::CARD_LAST4]); + $payment->setCcExpMonth($creditCard[self::CARD_EXP_MONTH]); + $payment->setCcExpYear($creditCard[self::CARD_EXP_YEAR]); + + + $payment->setCcType($this->getCreditCardType($creditCard[self::CARD_TYPE])); + + // set card details to additional info + $payment->setAdditionalInformation(self::CARD_NUMBER, 'xxxx-' . $creditCard[self::CARD_LAST4]); + $payment->setAdditionalInformation(OrderPaymentInterface::CC_TYPE, $creditCard[self::CARD_TYPE]); + } + + /** + * Get type of credit card mapped from Braintree + * @param string $type + * @return array + */ + private function getCreditCardType($type) + { + $replaced = str_replace(' ', '-', strtolower($type)); + $mapper = $this->config->getCctypesMapper(); + + return $mapper[$replaced]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php b/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php new file mode 100644 index 0000000000000..8cf801dafc3b7 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php @@ -0,0 +1,72 @@ +transaction; + /** @var \Magento\Sales\Model\Order\Payment $payment */ + $payment = $details->getPayment(); + ContextHelper::assertOrderPayment($payment); + + $payment->setTransactionId($transaction->id); + $payment->setCcTransId($transaction->id); + $payment->setLastTransId($transaction->id); + $payment->setIsTransactionClosed(false); + + //remove previously set payment nonce + $payment->unsAdditionalInformation(DataAssignObserver::PAYMENT_METHOD_NONCE); + foreach ($this->additionalInformationMapping as $item) { + if (!isset($transaction->$item)) { + continue; + } + $payment->setAdditionalInformation($item, $transaction->$item); + } + } +} diff --git a/app/code/Magento/BraintreeTwo/Gateway/Validator/ResponseValidator.php b/app/code/Magento/BraintreeTwo/Gateway/Validator/ResponseValidator.php new file mode 100644 index 0000000000000..48787ed97c899 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Gateway/Validator/ResponseValidator.php @@ -0,0 +1,63 @@ +createResult( + $this->validateSuccess($response) + && $this->validateErrors($response) + && $this->validateTransactionStatus($response), + [__('Transaction has been declined, please, try again later.')] + ); + + return $result; + } + + /** + * @param object $response + * @return bool + */ + private function validateSuccess($response) + { + return property_exists($response, 'success') && $response->success === true; + } + + /** + * @param object $response + * @return bool + */ + private function validateErrors($response) + { + return !(property_exists($response, 'errors') && $response->errors->deepSize() > 0); + } + + /** + * @param object $response + * @return bool + */ + private function validateTransactionStatus($response) + { + return in_array( + $response->transaction->status, + [\Braintree_Transaction::AUTHORIZED, \Braintree_Transaction::SUBMITTED_FOR_SETTLEMENT] + ); + } +} diff --git a/app/code/Magento/BraintreeTwo/Helper/CcType.php b/app/code/Magento/BraintreeTwo/Helper/CcType.php new file mode 100644 index 0000000000000..4f553c4430dd5 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Helper/CcType.php @@ -0,0 +1,48 @@ +ccTypeSource = $ccTypeSource; + } + + /** + * All possible credit card types + * + * @return array + */ + public function getCcTypes() + { + if (!$this->ccTypes) { + $this->ccTypes = $this->ccTypeSource->toOptionArray(); + } + return $this->ccTypes; + } +} diff --git a/app/code/Magento/BraintreeTwo/Helper/Country.php b/app/code/Magento/BraintreeTwo/Helper/Country.php new file mode 100644 index 0000000000000..88a81e499caea --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Helper/Country.php @@ -0,0 +1,50 @@ +collectionFactory = $factory; + } + + /** + * Returns countries array + * + * @return array + */ + public function getCountries() + { + if (!$this->countries) { + $this->countries = $this->collectionFactory->create() + ->addFieldToFilter('country_id', ['nin' => CountryConfig::$excludedCountries]) + ->loadData() + ->toOptionArray(false); + } + return $this->countries; + } +} diff --git a/app/code/Magento/BraintreeTwo/LICENSE.txt b/app/code/Magento/BraintreeTwo/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/BraintreeTwo/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/LICENSE_AFL.txt b/app/code/Magento/BraintreeTwo/LICENSE_AFL.txt new file mode 100644 index 0000000000000..87943b95d43a5 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeClientToken.php b/app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeClientToken.php new file mode 100644 index 0000000000000..02c79825e634d --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Adapter/BraintreeClientToken.php @@ -0,0 +1,31 @@ +_paymentConfig->getCcTypes(); + } +} diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/Environment.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/Environment.php new file mode 100644 index 0000000000000..3990ead5cf187 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/Environment.php @@ -0,0 +1,36 @@ + self::ENVIRONMENT_SANDBOX, + 'label' => 'Sandbox', + ], + [ + 'value' => self::ENVIRONMENT_PRODUCTION, + 'label' => 'Production' + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/PaymentAction.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/PaymentAction.php new file mode 100644 index 0000000000000..b23cac564ee68 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/PaymentAction.php @@ -0,0 +1,34 @@ + AbstractMethod::ACTION_AUTHORIZE, + 'label' => __('Authorize'), + ], + [ + 'value' => AbstractMethod::ACTION_AUTHORIZE_CAPTURE, + 'label' => __('Authorize and Capture'), + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php new file mode 100644 index 0000000000000..65cd8f9e6665b --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php @@ -0,0 +1,76 @@ +countryCollection = $countryCollection; + } + + /** + * @param bool $isMultiselect + * @return array + */ + public function toOptionArray($isMultiselect = false) + { + if (!$this->options) { + $this->options = $this->countryCollection + ->addFieldToFilter('country_id', ['nin' => self::$excludedCountries]) + ->loadData() + ->toOptionArray(false); + } + + $options = $this->options; + if (!$isMultiselect) { + array_unshift($options, ['value' => '', 'label' => __('--Please Select--')]); + } + + return $options; + } + + /** + * If country is in list of restricted (not supported by Braintree) + * + * @param string $countryId + * @return boolean + */ + public function isCountryRestricted($countryId) + { + $keys = array_flip(self::$excludedCountries); + return isset($keys[$countryId]); + } +} diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php new file mode 100644 index 0000000000000..80af387a5cadf --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php @@ -0,0 +1,108 @@ +mathRandom = $mathRandom; + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + } + + /** + * Prepare data before save + * + * @return $this + */ + public function beforeSave() + { + $value = $this->getValue(); + $result = []; + foreach ($value as $data) { + if (empty($data['country_id']) || empty($data['cc_types'])) { + continue; + } + $country = $data['country_id']; + if (array_key_exists($country, $result)) { + $result[$country] = array_merge($result[$country], $data['cc_types']); + // filter and reindex array + $result[$country] = array_values(array_unique($result[$country])); + } else { + $result[$country] = $data['cc_types']; + } + } + $this->setValue(serialize($result)); + return $this; + } + + /** + * Process data after load + * + * @return $this + */ + public function afterLoad() + { + $value = unserialize($this->getValue()); + if (is_array($value)) { + $value = $this->encodeArrayFieldValue($value); + $this->setValue($value); + } + return $this; + } + + /** + * Encode value to be used in \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray + * + * @param array $value + * @return array + */ + protected function encodeArrayFieldValue(array $value) + { + $result = []; + foreach ($value as $country => $creditCardType) { + $id = $this->mathRandom->getUniqueHash('_'); + $result[$id] = ['country_id' => $country, 'cc_types' => $creditCardType]; + } + return $result; + } +} diff --git a/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php b/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php new file mode 100644 index 0000000000000..300ecd944abd4 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php @@ -0,0 +1,53 @@ +config = $config; + } + + /** + * Retrieve assoc array of checkout configuration + * + * @return array + */ + public function getConfig() + { + return [ + 'payment' => [ + self::CODE => [ + 'clientToken' => $this->config->getClientToken(), + 'cctypesMapper' => $this->config->getCctypesMapper(), + 'sdkUrl' => $this->config->getValue(Config::KEY_SDK_URL), + 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig(), + 'availableCardTypes' => $this->config->getCcAvailableCardTypes(), + 'useCvv' => $this->config->useCvv() + ], + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php new file mode 100644 index 0000000000000..1905de8c79987 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php @@ -0,0 +1,34 @@ +readMethodArgument($observer); + $data = $this->readDataArgument($observer); + $paymentInfo = $method->getInfoInstance(); + if ($data->getDataByKey('payment_method_nonce') !== null) { + $paymentInfo->setAdditionalInformation( + self::PAYMENT_METHOD_NONCE, + $data->getDataByKey('payment_method_nonce') + ); + } + } +} diff --git a/app/code/Magento/BraintreeTwo/README.md b/app/code/Magento/BraintreeTwo/README.md new file mode 100644 index 0000000000000..e0f041d9698ff --- /dev/null +++ b/app/code/Magento/BraintreeTwo/README.md @@ -0,0 +1 @@ +Module Magento\BraintreeTwo implements integration with the Braintree payment system. \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php new file mode 100644 index 0000000000000..ce0e6d4535966 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php @@ -0,0 +1,146 @@ + 'American Express', + 'VI' => 'Visa', + 'MC' => 'MasterCard', + 'DI' => 'Discover', + 'JBC' => 'JBC', + 'OT' => 'Other' + ]; + + public static $configCardTypes = [ + 'AE', 'VI', 'MC', 'DI', 'JBC' + ]; + + /** + * @var \Magento\BraintreeTwo\Block\Form + */ + private $block; + + /** + * @var \Magento\Payment\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentConfig; + + /** + * @var \Magento\Backend\Model\Session\Quote|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionQuote; + + /** + * @var \Magento\BraintreeTwo\Gateway\Config\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $gatewayConfig; + + protected function setUp() + { + $this->initPaymentConfigMock(); + $this->initSessionQuoteMock(); + $this->initGatewayConfigMock(); + + $managerHelper = new ObjectManager($this); + $this->block = $managerHelper->getObject(Form::class, [ + 'paymentConfig' => $this->paymentConfig, + 'sessionQuote' => $this->sessionQuote, + 'gatewayConfig' => $this->gatewayConfig + ]); + } + + /** + * @covers \Magento\BraintreeTwo\Block\Form::getCcAvailableTypes + * @param string $countryId + * @param array $availableTypes + * @param array $expected + * @dataProvider countryCardTypesDataProvider + */ + public function testGetCcAvailableTypes($countryId, array $availableTypes, array $expected) + { + $this->sessionQuote->expects(static::once()) + ->method('getCountryId') + ->willReturn($countryId); + + $this->gatewayConfig->expects(static::once()) + ->method('getCcAvailableCardTypes') + ->willReturn(self::$configCardTypes); + + $this->gatewayConfig->expects(static::once()) + ->method('getCountryAvailableCardTypes') + ->with($countryId) + ->willReturn($availableTypes); + + $result = $this->block->getCcAvailableTypes(); + static::assertEquals($expected, array_values($result)); + } + + /** + * Get country card types testing data + * @return array + */ + public function countryCardTypesDataProvider() + { + return [ + ['US', ['AE', 'VI'], ['American Express', 'Visa']], + ['UK', ['VI'], ['Visa']], + ['CA', ['MC'], ['MasterCard']], + ['UA', [], ['American Express', 'Visa', 'MasterCard', 'Discover', 'JBC']] + ]; + } + + /** + * Create mock for payment config + */ + private function initPaymentConfigMock() + { + $this->paymentConfig = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getCcTypes']) + ->getMock(); + + $this->paymentConfig->expects(static::once()) + ->method('getCcTypes') + ->willReturn(self::$baseCardTypes); + } + + /** + * Create mock for session quote + */ + private function initSessionQuoteMock() + { + $this->sessionQuote = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->setMethods(['getQuote', 'getBillingAddress', 'getCountryId', '__wakeup']) + ->getMock(); + + $this->sessionQuote->expects(static::once()) + ->method('getQuote') + ->willReturnSelf(); + $this->sessionQuote->expects(static::once()) + ->method('getBillingAddress') + ->willReturnSelf(); + } + + /** + * Create mock for gateway config + */ + private function initGatewayConfigMock() + { + $this->gatewayConfig = $this->getMockBuilder(GatewayConfig::class) + ->disableOriginalConstructor() + ->setMethods(['getCountryAvailableCardTypes', 'getCcAvailableCardTypes']) + ->getMock(); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php new file mode 100644 index 0000000000000..988933fdad181 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php @@ -0,0 +1,285 @@ +scopeConfigMock = $this->getMock(ScopeConfigInterface::class); + $this->braintreeConfigurationMock = $this->getMock(BraintreeConfiguration::class); + $this->braintreeClientTokenMock = $this->getMock(BraintreeClientToken::class); + + $this->model = new Config( + $this->scopeConfigMock, + $this->braintreeConfigurationMock, + $this->braintreeClientTokenMock, + self::METHOD_CODE + ); + } + + /** + * Test initCredentials call in constructor when payment is active + * + * @covers \Magento\BraintreeTwo\Gateway\Config\Config::initCredentials + */ + public function testConstructorActive() + { + $environment = \Magento\BraintreeTwo\Model\Adminhtml\Source\Environment::ENVIRONMENT_PRODUCTION; + $merchantId = 'merchantId'; + $publicKey = 'public_key'; + $privateKey = 'private_key'; + + $this->scopeConfigMock->expects(static::exactly(5)) + ->method('getValue') + ->willReturnMap( + [ + [$this->getPath(Config::KEY_ACTIVE), ScopeInterface::SCOPE_STORE, null, 1], + [$this->getPath(Config::KEY_ENVIRONMENT), ScopeInterface::SCOPE_STORE, null, $environment], + [$this->getPath(Config::KEY_MERCHANT_ID), ScopeInterface::SCOPE_STORE, null, $merchantId], + [$this->getPath(Config::KEY_PUBLIC_KEY), ScopeInterface::SCOPE_STORE, null, $publicKey], + [$this->getPath(Config::KEY_PRIVATE_KEY), ScopeInterface::SCOPE_STORE, null, $privateKey] + ] + ); + + $this->braintreeConfigurationMock->expects(static::once()) + ->method('environment') + ->with($environment); + $this->braintreeConfigurationMock->expects(static::once()) + ->method('merchantId') + ->with($merchantId); + $this->braintreeConfigurationMock->expects(static::once()) + ->method('publicKey') + ->with($publicKey); + $this->braintreeConfigurationMock->expects(static::once()) + ->method('privateKey') + ->with($privateKey); + + $this->model = new Config( + $this->scopeConfigMock, + $this->braintreeConfigurationMock, + $this->braintreeClientTokenMock, + self::METHOD_CODE + ); + } + + /** + * Test constructor when payment is inactive + */ + public function testConstructorInActive() + { + $this->scopeConfigMock->expects(static::once()) + ->method('getValue') + ->with($this->getPath(Config::KEY_ACTIVE), ScopeInterface::SCOPE_STORE, null) + ->willReturn(0); + + $this->braintreeConfigurationMock->expects(static::never()) + ->method('environment'); + $this->braintreeConfigurationMock->expects(static::never()) + ->method('merchantId'); + $this->braintreeConfigurationMock->expects(static::never()) + ->method('publicKey'); + $this->braintreeConfigurationMock->expects(static::never()) + ->method('privateKey'); + + $this->model = new Config( + $this->scopeConfigMock, + $this->braintreeConfigurationMock, + $this->braintreeClientTokenMock, + self::METHOD_CODE + ); + } + + public function testGetClientToken() + { + $this->braintreeClientTokenMock->expects(static::once()) + ->method('generate') + ->willReturn(self::CLIENT_TOKEN); + + static::assertEquals(self::CLIENT_TOKEN, $this->model->getClientToken()); + } + + /** + * @param string $value + * @param array $expected + * @dataProvider getCountrySpecificCardTypeConfigDataProvider + */ + public function testGetCountrySpecificCardTypeConfig($value, $expected) + { + $this->scopeConfigMock->expects(static::once()) + ->method('getValue') + ->with($this->getPath(Config::KEY_COUNTRY_CREDIT_CARD), ScopeInterface::SCOPE_STORE, null) + ->willReturn($value); + + static::assertEquals( + $expected, + $this->model->getCountrySpecificCardTypeConfig() + ); + } + + /** + * @return array + */ + public function getCountrySpecificCardTypeConfigDataProvider() + { + return [ + [ + serialize(['GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB']]), + ['GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB']] + ], + [ + '', + [] + ] + ]; + } + + /** + * @param string $value + * @param array $expected + * @dataProvider getCcAvailableCardTypesDataProvider + */ + public function testGetCcAvailableCardTypes($value, $expected) + { + $this->scopeConfigMock->expects(static::once()) + ->method('getValue') + ->with($this->getPath(Config::KEY_CC_TYPES), ScopeInterface::SCOPE_STORE, null) + ->willReturn($value); + + static::assertEquals( + $expected, + $this->model->getCcAvailableCardTypes() + ); + } + + /** + * @return array + */ + public function getCcAvailableCardTypesDataProvider() + { + return [ + [ + 'AE,VI,MC,DI,JCB', + ['AE', 'VI', 'MC', 'DI', 'JCB'] + ], + [ + '', + [] + ] + ]; + } + + /** + * @param string $value + * @param array $expected + * @dataProvider getCctypesMapperDataProvider + */ + public function testGetCctypesMapper($value, $expected) + { + $this->scopeConfigMock->expects(static::once()) + ->method('getValue') + ->with($this->getPath(Config::KEY_CC_TYPES_BRAINTREE_MAPPER), ScopeInterface::SCOPE_STORE, null) + ->willReturn($value); + + static::assertEquals( + $expected, + $this->model->getCctypesMapper() + ); + } + + /** + * @return array + */ + public function getCctypesMapperDataProvider() + { + return [ + [ + '{"visa":"VI","american-express":"AE"}', + ['visa' => 'VI', 'american-express' => 'AE'] + ], + [ + '{invalid json}', + [] + ], + [ + '', + [] + ] + ]; + } + + /** + * @covers \Magento\BraintreeTwo\Gateway\Config\Config::getCountryAvailableCardTypes + * @dataProvider getCountrySpecificCardTypeConfigDataProvider + */ + public function testCountryAvailableCardTypes($data, $countryData) + { + $this->scopeConfigMock->expects(static::any()) + ->method('getValue') + ->with($this->getPath(Config::KEY_COUNTRY_CREDIT_CARD), ScopeInterface::SCOPE_STORE, null) + ->willReturn($data); + + foreach ($countryData as $countryId => $types) { + $result = $this->model->getCountryAvailableCardTypes($countryId); + static::assertEquals($types, $result); + } + } + + public function testUseCvv() + { + $this->scopeConfigMock->expects(static::any()) + ->method('getValue') + ->with($this->getPath(Config::KEY_USE_CVV), ScopeInterface::SCOPE_STORE, null) + ->willReturn(1); + + static::assertEquals(true, $this->model->useCvv()); + } + + /** + * Return config path + * + * @param string $field + * @return string + */ + private function getPath($field) + { + return sprintf(Config::DEFAULT_PATH_PATTERN, self::METHOD_CODE, $field); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php new file mode 100644 index 0000000000000..af1ad2a8f74f5 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/Client/TransactionSaleTest.php @@ -0,0 +1,142 @@ +loggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->braintreeTransactionMock = $this->getMockBuilder(BraintreeTransaction::class) + ->getMock(); + + $this->model = new TransactionSale($this->loggerMock, $this->braintreeTransactionMock); + } + + /** + * Run test placeRequest method (exception) + * + * @return void + * + * @expectedException \Magento\Payment\Gateway\Http\ClientException + * @expectedExceptionMessage Test messages + */ + public function testPlaceRequestException() + { + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionSale::class, + 'response' => [] + ] + ); + + $this->braintreeTransactionMock->expects($this->once()) + ->method('sale') + ->willThrowException(new \Exception('Test messages')); + + /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */ + $transferObjectMock = $this->getTransferObjectMock(); + + $this->model->placeRequest($transferObjectMock); + } + + /** + * Run test placeRequest method + * + * @return void + */ + public function testPlaceRequestSuccess() + { + $response = $this->getResponseObject(); + $this->braintreeTransactionMock->expects($this->once()) + ->method('sale') + ->with($this->getTransferData()) + ->willReturn($response) + ; + + $this->loggerMock->expects($this->once()) + ->method('debug') + ->with( + [ + 'request' => $this->getTransferData(), + 'client' => TransactionSale::class, + 'response' => ['success' => 1] + ] + ); + + $actualResult = $this->model->placeRequest($this->getTransferObjectMock()); + + $this->assertTrue(is_object($actualResult['object'])); + $this->assertEquals(['object' => $response], $actualResult); + } + + /** + * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getTransferObjectMock() + { + $transferObjectMock = $this->getMock(TransferInterface::class); + $transferObjectMock->expects($this->once()) + ->method('getBody') + ->willReturn($this->getTransferData()); + + return $transferObjectMock; + } + + /** + * @return \stdClass + */ + private function getResponseObject() + { + $obj = new \stdClass; + $obj->success = true; + + return $obj; + } + + /** + * @return array + */ + private function getTransferData() + { + return [ + 'test-data-key' => 'test-data-value' + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/TransferFactoryTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/TransferFactoryTest.php new file mode 100644 index 0000000000000..f0690b6bc9467 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Http/TransferFactoryTest.php @@ -0,0 +1,57 @@ +transferBuilder = $this->getMock(TransferBuilder::class); + $this->transferMock = $this->getMock(TransferInterface::class); + + $this->transferFactory = new TransferFactory( + $this->transferBuilder + ); + } + + public function testCreate() + { + $request = ['data1', 'data2']; + + $this->transferBuilder->expects($this->once()) + ->method('setBody') + ->with($request) + ->willReturnSelf(); + + $this->transferBuilder->expects($this->once()) + ->method('build') + ->willReturn($this->transferMock); + + $this->assertEquals($this->transferMock, $this->transferFactory->create($request)); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/AddressDataBuilderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/AddressDataBuilderTest.php new file mode 100644 index 0000000000000..e2a432e9fa36c --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/AddressDataBuilderTest.php @@ -0,0 +1,182 @@ +paymentDOMock = $this->getMock(PaymentDataObjectInterface::class); + $this->orderMock = $this->getMock(OrderAdapterInterface::class); + + $this->builder = new AddressDataBuilder(); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Payment data object should be provided + */ + public function testBuildReadPaymentException() + { + $buildSubject = [ + 'payment' => null, + ]; + + $this->builder->build($buildSubject); + } + + public function testBuildNoAddresses() + { + $this->paymentDOMock->expects($this->once()) + ->method('getOrder') + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once()) + ->method('getShippingAddress') + ->willReturn(null); + $this->orderMock->expects($this->once()) + ->method('getBillingAddress') + ->willReturn(null); + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + static::assertEmpty($this->builder->build($buildSubject)); + } + + /** + * @param array $addressData + * @param array $expectedResult + * + * @dataProvider dataProviderBuild + */ + public function testBuild($addressData, $expectedResult) + { + $addressMock = $this->getAddressMock($addressData); + + $this->paymentDOMock->expects($this->once()) + ->method('getOrder') + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($addressMock); + $this->orderMock->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($addressMock); + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expectedResult, $this->builder->build($buildSubject)); + } + + /** + * @return array + */ + public function dataProviderBuild() + { + return [ + [ + [ + 'first_name' => 'John', + 'last_name' => 'Smith', + 'company' => 'Magento', + 'street_1' => 'street1', + 'street_2' => 'street2', + 'city' => 'Chicago', + 'region_code' => 'IL', + 'country_id' => 'US', + 'post_code' => '00000' + ], + [ + AddressDataBuilder::SHIPPING_ADDRESS => [ + AddressDataBuilder::FIRST_NAME => 'John', + AddressDataBuilder::LAST_NAME => 'Smith', + AddressDataBuilder::COMPANY => 'Magento', + AddressDataBuilder::STREET_ADDRESS => 'street1', + AddressDataBuilder::EXTENDED_ADDRESS => 'street2', + AddressDataBuilder::LOCALITY => 'Chicago', + AddressDataBuilder::REGION => 'IL', + AddressDataBuilder::POSTAL_CODE => '00000', + AddressDataBuilder::COUNTRY_CODE => 'US' + + ], + AddressDataBuilder::BILLING_ADDRESS => [ + AddressDataBuilder::FIRST_NAME => 'John', + AddressDataBuilder::LAST_NAME => 'Smith', + AddressDataBuilder::COMPANY => 'Magento', + AddressDataBuilder::STREET_ADDRESS => 'street1', + AddressDataBuilder::EXTENDED_ADDRESS => 'street2', + AddressDataBuilder::LOCALITY => 'Chicago', + AddressDataBuilder::REGION => 'IL', + AddressDataBuilder::POSTAL_CODE => '00000', + AddressDataBuilder::COUNTRY_CODE => 'US' + ] + ] + ] + ]; + } + + /** + * @param array $addressData + * @return AddressAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getAddressMock($addressData) + { + $addressMock = $this->getMock(AddressAdapterInterface::class); + + $addressMock->expects(static::exactly(2)) + ->method('getFirstname') + ->willReturn($addressData['first_name']); + $addressMock->expects(static::exactly(2)) + ->method('getLastname') + ->willReturn($addressData['last_name']); + $addressMock->expects(static::exactly(2)) + ->method('getCompany') + ->willReturn($addressData['company']); + $addressMock->expects(static::exactly(2)) + ->method('getStreetLine1') + ->willReturn($addressData['street_1']); + $addressMock->expects(static::exactly(2)) + ->method('getStreetLine2') + ->willReturn($addressData['street_2']); + $addressMock->expects(static::exactly(2)) + ->method('getCity') + ->willReturn($addressData['city']); + $addressMock->expects(static::exactly(2)) + ->method('getRegionCode') + ->willReturn($addressData['region_code']); + $addressMock->expects(static::exactly(2)) + ->method('getPostcode') + ->willReturn($addressData['post_code']); + $addressMock->expects(static::exactly(2)) + ->method('getCountryId') + ->willReturn($addressData['country_id']); + + return $addressMock; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php new file mode 100644 index 0000000000000..79148e1f25b01 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php @@ -0,0 +1,128 @@ +paymentDOMock = $this->getMock(PaymentDataObjectInterface::class); + $this->orderMock = $this->getMock(OrderAdapterInterface::class); + + $this->builder = new CustomerDataBuilder(); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Payment data object should be provided + */ + public function testBuildReadPaymentException() + { + $buildSubject = [ + 'payment' => null, + ]; + + $this->builder->build($buildSubject); + } + + /** + * @param array $billingData + * @param array $expectedResult + * + * @dataProvider dataProviderBuild + */ + public function testBuild($billingData, $expectedResult) + { + $billingMock = $this->getBillingMock($billingData); + + $this->paymentDOMock->expects($this->once()) + ->method('getOrder') + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($billingMock); + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expectedResult, $this->builder->build($buildSubject)); + } + + /** + * @return array + */ + public function dataProviderBuild() + { + return [ + [ + [ + 'first_name' => 'John', + 'last_name' => 'Smith', + 'company' => 'Magento', + 'phone' => '555-555-555', + 'email' => 'john@magento.com' + ], + [ + CustomerDataBuilder::CUSTOMER => [ + CustomerDataBuilder::FIRST_NAME => 'John', + CustomerDataBuilder::LAST_NAME => 'Smith', + CustomerDataBuilder::COMPANY => 'Magento', + CustomerDataBuilder::PHONE => '555-555-555', + CustomerDataBuilder::EMAIL => 'john@magento.com', + ] + ] + ] + ]; + } + + /** + * @param array $billingData + * @return AddressAdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getBillingMock($billingData) + { + $addressMock = $this->getMock(AddressAdapterInterface::class); + + $addressMock->expects(static::once()) + ->method('getFirstname') + ->willReturn($billingData['first_name']); + $addressMock->expects(static::once()) + ->method('getLastname') + ->willReturn($billingData['last_name']); + $addressMock->expects(static::once()) + ->method('getCompany') + ->willReturn($billingData['company']); + $addressMock->expects(static::once()) + ->method('getTelephone') + ->willReturn($billingData['phone']); + $addressMock->expects(static::once()) + ->method('getEmail') + ->willReturn($billingData['email']); + + return $addressMock; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php new file mode 100644 index 0000000000000..903f64da383d6 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php @@ -0,0 +1,109 @@ +paymentDO = $this->getMock(PaymentDataObjectInterface::class); + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->paymentMock = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->builder = new PaymentDataBuilder($this->configMock); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Payment data object should be provided + */ + public function testBuildReadPaymentException() + { + $buildSubject = []; + + $this->builder->build($buildSubject); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Amount should be provided + */ + public function testBuildReadAmountException() + { + $buildSubject = [ + 'payment' => $this->paymentDO, + 'amount' => null + ]; + + $this->builder->build($buildSubject); + } + + public function testBuild() + { + $expectedResult = [ + PaymentDataBuilder::AMOUNT => 10.00, + PaymentDataBuilder::PAYMENT_METHOD_NONCE => self::PAYMENT_METHOD_NONCE, + PaymentDataBuilder::MERCHANT_ACCOUNT_ID => self::MERCHANT_ACCOUNT_ID + ]; + + $buildSubject = [ + 'payment' => $this->paymentDO, + 'amount' => 10.00 + ]; + + $this->paymentMock->expects(static::once()) + ->method('getAdditionalInformation') + ->with(DataAssignObserver::PAYMENT_METHOD_NONCE) + ->willReturn(self::PAYMENT_METHOD_NONCE); + + $this->configMock->expects(static::once()) + ->method('getValue') + ->with(Config::KEY_MERCHANT_ACCOUNT_ID) + ->willReturn(self::MERCHANT_ACCOUNT_ID); + + $this->paymentDO->expects(static::once()) + ->method('getPayment') + ->willReturn($this->paymentMock); + + static::assertEquals( + $expectedResult, + $this->builder->build($buildSubject) + ); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/SettlementDataBuilderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/SettlementDataBuilderTest.php new file mode 100644 index 0000000000000..0dcd195a7e0df --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Request/SettlementDataBuilderTest.php @@ -0,0 +1,23 @@ +assertEquals( + [ + 'options' => [ + SettlementDataBuilder::SUBMIT_FOR_SETTLEMENT => true + ] + ], + (new SettlementDataBuilder())->build([]) + ); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php new file mode 100644 index 0000000000000..1bdce17368595 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php @@ -0,0 +1,152 @@ +initConfigMock(); + + $helper = new ObjectManager($this); + $this->cardHandler = $helper->getObject(CardDetailsHandler::class, ['config' => $this->config]); + } + + /** + * @covers \Magento\BraintreeTwo\Gateway\Response\CardDetailsHandler::handle + */ + public function testHandle() + { + $paymentData = $this->getPaymentDataObjectMock(); + $subject['payment'] = $paymentData; + + $response = [ + 'object' => $this->getBraintreeTransaction() + ]; + + $this->payment->expects(static::once()) + ->method('setCcLast4'); + $this->payment->expects(static::once()) + ->method('setCcExpMonth'); + $this->payment->expects(static::once()) + ->method('setCcExpYear'); + $this->payment->expects(static::once()) + ->method('setCcType'); + $this->payment->expects(static::exactly(2)) + ->method('setAdditionalInformation'); + + $this->cardHandler->handle($subject, $response); + } + + /** + * Create mock for gateway config + */ + private function initConfigMock() + { + $this->config = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getCctypesMapper']) + ->getMock(); + + $this->config->expects(static::once()) + ->method('getCctypesMapper') + ->willReturn([ + 'american-express' => 'AE', + 'discover' => 'DI', + 'jcb' => 'JCB', + 'mastercard' => 'MC', + 'master-card' => 'MC', + 'visa' => 'VI' + ]); + } + + /** + * Create mock for payment data object and order payment + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getPaymentDataObjectMock() + { + $this->payment = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->setMethods([ + 'setCcLast4', + 'setCcExpMonth', + 'setCcExpYear', + 'setCcType', + 'setAdditionalInformation', + ]) + ->getMock(); + + $mock = $this->getMockBuilder(PaymentDataObject::class) + ->setMethods(['getPayment']) + ->disableOriginalConstructor() + ->getMock(); + + $mock->expects(static::once()) + ->method('getPayment') + ->willReturn($this->payment); + + return $mock; + } + + /** + * Create Braintree transaction + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getBraintreeTransaction() + { + $attributes = [ + 'creditCard' => [ + 'bin' => '5421', + 'cardType' => 'American Express', + 'expirationMonth' => 12, + 'expirationYear' => 21, + 'last4' => 1231 + ] + ]; + $transaction = Braintree_Transaction::factory($attributes); + + $mock = $this->getMockBuilder(Braintree_Result_Successful::class) + ->disableOriginalConstructor() + ->setMethods(['__get']) + ->getMock(); + + $mock->expects(static::once()) + ->method('__get') + ->with('transaction') + ->willReturn($transaction); + + return $mock; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php new file mode 100644 index 0000000000000..7dfd216703d60 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php @@ -0,0 +1,124 @@ +paymentHandler = $helper->getObject(PaymentDetailsHandler::class); + } + + /** + * @covers \Magento\BraintreeTwo\Gateway\Response\PaymentDetailsHandler::handle + */ + public function testHandle() + { + $paymentData = $this->getPaymentDataObjectMock(); + $subject['payment'] = $paymentData; + + $this->payment->expects(static::once()) + ->method('setTransactionId'); + $this->payment->expects(static::once()) + ->method('setCcTransId'); + $this->payment->expects(static::once()) + ->method('setLastTransId'); + $this->payment->expects(static::once()) + ->method('setIsTransactionClosed'); + $this->payment->expects(static::exactly(6)) + ->method('setAdditionalInformation'); + + $response = [ + 'object' => $this->getBraintreeTransaction() + ]; + + $this->paymentHandler->handle($subject, $response); + } + + /** + * Create mock for payment data object and order payment + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getPaymentDataObjectMock() + { + $this->payment = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->setMethods([ + 'setTransactionId', + 'setCcTransId', + 'setLastTransId', + 'setAdditionalInformation', + 'setIsTransactionClosed', + ]) + ->getMock(); + + $mock = $this->getMockBuilder(PaymentDataObject::class) + ->setMethods(['getPayment']) + ->disableOriginalConstructor() + ->getMock(); + + $mock->expects(static::once()) + ->method('getPayment') + ->willReturn($this->payment); + + return $mock; + } + + /** + * Create Braintree transaction + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getBraintreeTransaction() + { + $attributes = [ + 'id' => self::TRANSACTION_ID, + 'avsPostalCodeResponseCode' => 'M', + 'avsStreetAddressResponseCode' => 'M', + 'cvvResponseCode' => 'M', + 'processorAuthorizationCode' => 'W1V8XK', + 'processorResponseCode' => '1000', + 'processorResponseText' => 'Approved', + ]; + + $transaction = Braintree_Transaction::factory($attributes); + + $mock = $this->getMockBuilder(Braintree_Result_Successful::class) + ->disableOriginalConstructor() + ->setMethods(['__get']) + ->getMock(); + + $mock->expects(static::once()) + ->method('__get') + ->with('transaction') + ->willReturn($transaction); + + return $mock; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Validator/ResponseValidatorTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Validator/ResponseValidatorTest.php new file mode 100644 index 0000000000000..cfcb03c3d1b35 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Validator/ResponseValidatorTest.php @@ -0,0 +1,142 @@ +resultInterfaceFactoryMock = $this->getMockBuilder( + 'Magento\Payment\Gateway\Validator\ResultInterfaceFactory' + )->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->responseValidator = new ResponseValidator($this->resultInterfaceFactoryMock); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Response does not exist + */ + public function testValidateReadResponseException() + { + $validationSubject = [ + 'response' => null + ]; + + $this->responseValidator->validate($validationSubject); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Response object does not exist + */ + public function testValidateReadResponseObjectException() + { + $validationSubject = [ + 'response' => ['object' => null] + ]; + + $this->responseValidator->validate($validationSubject); + } + + + /** + * Run test for validate method + * + * @param array $validationSubject + * @param bool $isValid + * @return void + * + * @dataProvider dataProviderTestValidate + */ + public function testValidate(array $validationSubject, $isValid) + { + /** @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMock(ResultInterface::class); + + $this->resultInterfaceFactoryMock->expects($this->once()) + ->method('create') + ->with([ + 'isValid' => $isValid, + 'failsDescription' => ['Transaction has been declined, please, try again later.'] + ]) + ->willReturn($resultMock); + + $actualMock = $this->responseValidator->validate($validationSubject); + + $this->assertEquals($resultMock, $actualMock); + } + + /** + * @return array + */ + public function dataProviderTestValidate() + { + $successTrue = new \stdClass(); + $successTrue->success = true; + $successTrue->transaction = new \stdClass(); + $successTrue->transaction->status = \Braintree_Transaction::AUTHORIZED; + + $successFalse = new \stdClass(); + $successFalse->success = false; + + $transactionDeclined = new \stdClass(); + $transactionDeclined->success = true; + $transactionDeclined->transaction = new \stdClass(); + $transactionDeclined->transaction->status = \Braintree_Transaction::SETTLEMENT_DECLINED; + + return [ + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $successTrue + ], + ], + 'isValid' => true, + ], + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $successFalse + ] + ], + 'isValid' => false + ], + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $transactionDeclined + ] + ], + 'isValid' => false + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CcTypeTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CcTypeTest.php new file mode 100644 index 0000000000000..39b65d1d74c52 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CcTypeTest.php @@ -0,0 +1,64 @@ +objectManager = new ObjectManager($this); + + $this->ccTypeSource = $this->getMockBuilder(CcTypeSource::class) + ->disableOriginalConstructor() + ->setMethods(['toOptionArray']) + ->getMock(); + + $this->helper = $this->objectManager->getObject(CcType::class, [ + 'ccTypeSource' => $this->ccTypeSource + ]); + } + + /** + * @covers \Magento\BraintreeTwo\Helper\CcType::getCcTypes + */ + public function testGetCcTypes() + { + $this->ccTypeSource->expects(static::once()) + ->method('toOptionArray') + ->willReturn([ + 'label' => 'VISA', 'value' => 'VI' + ]); + + $this->helper->getCcTypes(); + + $this->ccTypeSource->expects(static::never()) + ->method('toOptionArray'); + + $this->helper->getCcTypes(); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php new file mode 100644 index 0000000000000..411311e0433ad --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php @@ -0,0 +1,94 @@ +objectManager = new ObjectManager($this); + + $collectionFactory = $this->getCollectionFactoryMock(); + + $this->helper = $this->objectManager->getObject(Country::class, [ + 'factory' => $collectionFactory + ]); + } + + /** + * @covers \Magento\BraintreeTwo\Helper\Country::getCountries + */ + public function testGetCountries() + { + $this->collection->expects(static::once()) + ->method('toOptionArray') + ->willReturn([ + ['value' => 'US', 'label' => 'United States'], + ['value' => 'UK', 'label' => 'United Kingdom'], + ]); + + $this->helper->getCountries(); + + $this->collection->expects(static::never()) + ->method('toOptionArray'); + + $this->helper->getCountries(); + } + + /** + * Create mock for country collection factory + */ + protected function getCollectionFactoryMock() + { + $this->collection = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->setMethods(['addFieldToFilter', 'loadData', 'toOptionArray', '__wakeup']) + ->getMock(); + + $this->collection->expects(static::any()) + ->method('addFieldToFilter') + ->willReturnSelf(); + + $this->collection->expects(static::any()) + ->method('loadData') + ->willReturnSelf(); + + $collectionFactory = $this->getMockBuilder('\Magento\Directory\Model\ResourceModel\Country\CollectionFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $collectionFactory->expects(static::once()) + ->method('create') + ->willReturn($this->collection); + + return $collectionFactory; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryCreditCardTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryCreditCardTest.php new file mode 100644 index 0000000000000..99ed36038929e --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryCreditCardTest.php @@ -0,0 +1,190 @@ +resourceMock = $this->getMockForAbstractClass(AbstractResource::class); + $this->mathRandomMock = $this->getMockBuilder(Random::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new ObjectManager($this); + $this->model = $this->objectManager->getObject( + CountryCreditCard::class, + [ + 'mathRandom' => $this->mathRandomMock, + 'resource' => $this->resourceMock, + ] + ); + } + + /** + * @dataProvider beforeSaveDataProvider + */ + public function testBeforeSave($value, $expectedValue) + { + $this->model->setValue($value); + $this->model->beforeSave(); + $this->assertEquals($expectedValue, $this->model->getValue()); + } + + /** + * Get data for testing credit card types + * @return array + */ + public function beforeSaveDataProvider() + { + return [ + 'empty_value' => [ + 'value' => [], + 'expected' => serialize([]), + ], + 'not_array' => [ + 'value' => ['US'], + 'expected' => serialize([]), + ], + 'array_with_invalid_format' => [ + 'value' => [ + [ + 'country_id' => 'US', + ], + ], + 'expected' => serialize([]), + ], + 'array_with_two_countries' => [ + 'value' => [ + [ + 'country_id' => 'AF', + 'cc_types' => ['AE', 'VI'] + ], + [ + 'country_id' => 'US', + 'cc_types' => ['AE', 'VI', 'MA'] + ], + '__empty' => "", + ], + 'expected' => serialize( + [ + 'AF' => ['AE', 'VI'], + 'US' => ['AE', 'VI', 'MA'], + ] + ), + ], + 'array_with_two_same_countries' => [ + 'value' => [ + [ + 'country_id' => 'AF', + 'cc_types' => ['AE', 'VI'] + ], + [ + 'country_id' => 'US', + 'cc_types' => ['AE', 'VI', 'MA'] + ], + [ + 'country_id' => 'US', + 'cc_types' => ['VI', 'OT'] + ], + '__empty' => "", + ], + 'expected' => serialize( + [ + 'AF' => ['AE', 'VI'], + 'US' => ['AE', 'VI', 'MA', 'OT'], + ] + ), + ], + ]; + } + + /** + * @dataProvider afterLoadDataProvider + */ + public function testAfterLoad($value, $hashData, $expected) + { + $this->model->setValue($value); + $index = 0; + foreach ($hashData as $hash) { + $this->mathRandomMock->expects(static::at($index)) + ->method('getUniqueHash') + ->willReturn($hash); + $index ++; + } + $this->model->afterLoad(); + $this->assertEquals($expected, $this->model->getValue()); + } + + /** + * Get data to test saved credit cards types + * @return array + */ + public function afterLoadDataProvider() + { + return [ + 'empty' => [ + 'value' => serialize([]), + 'randomHash' => [], + 'expected' => [], + ], + 'null' => [ + 'value' => null, + 'randomHash' => [], + 'expected' => null, + ], + 'valid_data' => [ + 'value' => serialize( + [ + 'US' => ['AE', 'VI', 'MA'], + 'AF' => ['AE', 'MA'], + ] + ), + 'randomHash' => ['hash_1', 'hash_2'], + 'expected' => [ + 'hash_1' => [ + 'country_id' => 'US', + 'cc_types' => ['AE', 'VI', 'MA'], + ], + 'hash_2' => [ + 'country_id' => 'AF', + 'cc_types' => ['AE', 'MA'], + ], + ] + ], + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php new file mode 100644 index 0000000000000..ac80f37cd0b34 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php @@ -0,0 +1,148 @@ +countryCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new ObjectManager($this); + $this->model = $this->objectManager->getObject( + Country::class, + [ + 'countryCollection' => $this->countryCollectionMock, + ] + ); + } + + /** + * @covers \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country::toOptionArray + */ + public function testToOptionArrayMultiSelect() + { + $countries = [ + [ + 'value' => 'US', + 'label' => 'United States', + ], + [ + 'value' => 'UK', + 'label' => 'United Kingdom', + ], + ]; + $this->initCountryCollectionMock($countries); + + $this->assertEquals($countries, $this->model->toOptionArray(true)); + } + + /** + * @covers \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country::toOptionArray + */ + public function testToOptionArray() + { + $countries = [ + [ + 'value' => 'US', + 'label' => 'United States', + ], + [ + 'value' => 'UK', + 'label' => 'United Kingdom', + ], + ]; + $this->initCountryCollectionMock($countries); + + $header = ['value' => '', 'label' => new Phrase('--Please Select--')]; + array_unshift($countries, $header); + + $this->assertEquals($countries, $this->model->toOptionArray()); + } + + /** + * @covers \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country::isCountryRestricted + * @param string $countryId + * @dataProvider countryDataProvider + */ + public function testIsCountryRestricted($countryId) + { + static::assertTrue($this->model->isCountryRestricted($countryId)); + } + + /** + * Get simple list of not available braintree countries + * @return array + */ + public function countryDataProvider() + { + return [ + ['MM'], + ['IR'], + ['SD'], + ['BY'], + ['CI'], + ['CD'], + ['CG'], + ['IQ'], + ['LR'], + ['LB'], + ['KP'], + ['SL'], + ['SY'], + ['ZW'], + ['AL'], + ['BA'], + ['MK'], + ['ME'], + ['RS'] + ]; + } + + /** + * Init country collection mock + * @param array $countries + */ + protected function initCountryCollectionMock(array $countries) + { + $this->countryCollectionMock->expects(static::once()) + ->method('addFieldToFilter') + ->with('country_id', ['nin' => Country::$excludedCountries]) + ->willReturnSelf(); + $this->countryCollectionMock->expects(static::once()) + ->method('loadData') + ->willReturnSelf(); + $this->countryCollectionMock->expects(static::once()) + ->method('toOptionArray') + ->willReturn($countries); + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php new file mode 100644 index 0000000000000..e9ee654cd954d --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -0,0 +1,89 @@ +configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Run test getConfig method + * + * @param array $config + * @param array $expected + * @dataProvider getConfigDataProvider + */ + public function testGetConfig($config, $expected) + { + $configProvider = new ConfigProvider($this->configMock); + foreach ($config as $method => $value) { + $this->configMock->expects(static::once()) + ->method($method) + ->willReturn($value); + } + $this->configMock->expects(static::once()) + ->method('getValue') + ->with(Config::KEY_SDK_URL) + ->willReturn(self::SDK_URL); + static::assertEquals($expected, $configProvider->getConfig()); + } + + /** + * @return array + */ + public function getConfigDataProvider() + { + return [ + [ + 'config' => [ + 'getClientToken' => 'token', + 'getCctypesMapper' => ['visa' => 'VI', 'american-express'=> 'AE'], + 'getCountrySpecificCardTypeConfig' => [ + 'GB' => ['VI', 'AE'], + 'US' => ['DI', 'JCB'] + ], + 'getCcAvailableCardTypes' => ['AE', 'VI', 'MC', 'DI', 'JCB'], + 'useCvv' => true + ], + 'expected' => [ + 'payment' => [ + ConfigProvider::CODE => [ + 'clientToken' => 'token', + 'cctypesMapper' => ['visa' => 'VI', 'american-express' => 'AE'], + 'sdkUrl' => self::SDK_URL, + 'countrySpecificCardTypes' =>[ + 'GB' => ['VI', 'AE'], + 'US' => ['DI', 'JCB'] + ], + 'availableCardTypes' => ['AE', 'VI', 'MC', 'DI', 'JCB'], + 'useCvv' => true + ] + ] + ] + ] + ]; + } +} diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Observer/DataAssignObserverTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Observer/DataAssignObserverTest.php new file mode 100644 index 0000000000000..870b8f1755a49 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Observer/DataAssignObserverTest.php @@ -0,0 +1,60 @@ +getMockBuilder(Event\Observer::class) + ->disableOriginalConstructor() + ->getMock(); + $event = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->getMock(); + $paymentMethodFacade = $this->getMock(MethodInterface::class); + $paymentInfoModel = $this->getMock(InfoInterface::class); + $dataObject = new DataObject( + [ + 'payment_method_nonce' => self::PAYMENT_METHOD_NONCE + ] + ); + $observerContainer->expects(static::atLeastOnce()) + ->method('getEvent') + ->willReturn($event); + $event->expects(static::exactly(2)) + ->method('getDataByKey') + ->willReturnMap( + [ + [AbstractDataAssignObserver::METHOD_CODE, $paymentMethodFacade], + [AbstractDataAssignObserver::DATA_CODE, $dataObject] + ] + ); + $paymentMethodFacade->expects(static::once()) + ->method('getInfoInstance') + ->willReturn($paymentInfoModel); + $paymentInfoModel->expects(static::once()) + ->method('setAdditionalInformation') + ->with( + 'payment_method_nonce', + self::PAYMENT_METHOD_NONCE + ); + $observer = new DataAssignObserver(); + $observer->execute($observerContainer); + } +} diff --git a/app/code/Magento/BraintreeTwo/composer.json b/app/code/Magento/BraintreeTwo/composer.json new file mode 100644 index 0000000000000..b84c2b082c65d --- /dev/null +++ b/app/code/Magento/BraintreeTwo/composer.json @@ -0,0 +1,29 @@ +{ + "name": "magento/module-braintree-two", + "description": "N/A", + "require": { + "php": "~5.5.0|~5.6.0|~7.0.0", + "magento/framework": "*", + "magento/magento-composer-installer": "*", + "magento/module-config": "*", + "magento/module-directory": "*", + "magento/module-payment": "*", + "magento/module-checkout": "*", + "magento/module-sales": "*", + "magento/module-backend": "*", + "braintree/braintree_php": "3.6.1" + }, + "type": "magento2-module", + "version": "100.0.2", + "license": [ + "proprietary" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\BraintreeTwo\\": "" + } + } +} \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/adminhtml/di.xml b/app/code/Magento/BraintreeTwo/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..6b455b63ff8b4 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/adminhtml/di.xml @@ -0,0 +1,21 @@ + + + + + + Magento\BraintreeTwo\Model\Ui\ConfigProvider + + + + + + 0 + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml b/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..cb97762b5077e --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml @@ -0,0 +1,117 @@ + + + + +
+ + + + 1 + complex braintree-section + Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Group + + + Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment + payment/braintreetwo/active + + + Magento\Config\Model\Config\Source\Yesno + payment/braintreetwo/active + + + + + + Click here to login to your existing Braintree account. Or to setup a new account and accept payments on your website, click here to signup for a Braintree account.]]> + + 1 + Magento\Config\Block\System\Config\Form\Fieldset + + + payment/braintreetwo/title + + + + Magento\BraintreeTwo\Model\Adminhtml\Source\Environment + payment/braintreetwo/environment + + + + Magento\BraintreeTwo\Model\Adminhtml\Source\PaymentAction + payment/braintreetwo/payment_action + + + + payment/braintreetwo/merchant_id + + + + payment/braintreetwo/public_key + Magento\Config\Model\Config\Backend\Encrypted + + + + payment/braintreetwo/private_key + Magento\Config\Model\Config\Backend\Encrypted + + + + + Magento\Config\Block\System\Config\Form\Fieldset + + + payment/braintreetwo/merchant_account_id + + + + Magento\Config\Model\Config\Source\Yesno + payment/braintreetwo/debug + + + + Magento\Config\Model\Config\Source\Yesno + Be sure to Enable AVS and/or CVV in Your Braintree Account in Settings/Processing Section + payment/braintreetwo/useccv + + + + Magento\BraintreeTwo\Model\Adminhtml\Source\CcType + payment/braintreetwo/cctypes + + + + validate-number + payment/braintreetwo/sort_order + + + + + Magento\Config\Block\System\Config\Form\Fieldset + + + Magento\Payment\Model\Config\Source\Allspecificcountries + payment/braintreetwo/allowspecific + + + + Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country + 1 + payment/braintreetwo/specificcountry + + + + Magento\BraintreeTwo\Block\Adminhtml\Form\Field\CountryCreditCard + Magento\BraintreeTwo\Model\Adminhtml\System\Config\CountryCreditCard + payment/braintreetwo/countrycreditcard + + + + +
+
+
diff --git a/app/code/Magento/BraintreeTwo/etc/config.xml b/app/code/Magento/BraintreeTwo/etc/config.xml new file mode 100644 index 0000000000000..7146b3b8e3739 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/config.xml @@ -0,0 +1,37 @@ + + + + + + + BraintreeTwoFacade + Credit Card (BraintreeTwo) + authorize + 0 + 1 + 1 + 1 + 1 + AE,VI,MC,DI,JCB + 1 + + 1 + processing + sandbox + 0 + + + + cvv,number + avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText + cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText + 1 + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/di.xml b/app/code/Magento/BraintreeTwo/etc/di.xml new file mode 100644 index 0000000000000..8b886b6cc5714 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/di.xml @@ -0,0 +1,128 @@ + + + + + + + + Magento\BraintreeTwo\Model\Ui\ConfigProvider::CODE + Magento\BraintreeTwo\Block\Form + Magento\BraintreeTwo\Block\Info + BraintreeTwoValueHandlerPool + BraintreeTwoValidatorPool + BraintreeTwoCommandPool + + + + + + + \Magento\BraintreeTwo\Model\Ui\ConfigProvider::CODE + + + + + + + Magento\BraintreeTwo\Gateway\Config\Config + + + + + + + + BraintreeTwoAuthorizeGatewayCommand + BraintreeTwoSaleGatewayCommand + + + + + + + + BraintreeTwoAuthorizeDataBuilder + Magento\BraintreeTwo\Gateway\Http\TransferFactory + Magento\BraintreeTwo\Gateway\Http\Client\TransactionSale + BraintreeTwoResponseHandler + Magento\BraintreeTwo\Gateway\Validator\ResponseValidator + + + + + + Magento\BraintreeTwo\Gateway\Request\CustomerDataBuilder + Magento\BraintreeTwo\Gateway\Request\PaymentDataBuilder + Magento\BraintreeTwo\Gateway\Request\AddressDataBuilder + + + + + + BraintreeTwoLogger + + + + + + + BraintreeTwoSaleDataBuilder + + + + + + BraintreeTwoAuthorizeDataBuilder + Magento\BraintreeTwo\Gateway\Request\SettlementDataBuilder + + + + + + + + + BraintreeTwoConfigValueHandler + + + + + + Magento\BraintreeTwo\Gateway\Config\Config + + + + + + Magento\BraintreeTwo\Gateway\Response\PaymentDetailsHandler + Magento\BraintreeTwo\Gateway\Response\CardDetailsHandler + + + + + + + + Magento\BraintreeTwo\Gateway\Config\Config + + + + + + BraintreeTwoCountryValidator + + + + + + + Magento\BraintreeTwo\Gateway\Config\Config + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/events.xml b/app/code/Magento/BraintreeTwo/etc/events.xml new file mode 100644 index 0000000000000..def66f4b308ed --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/events.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/frontend/di.xml b/app/code/Magento/BraintreeTwo/etc/frontend/di.xml new file mode 100644 index 0000000000000..b0238c838bbc0 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/frontend/di.xml @@ -0,0 +1,30 @@ + + + + + + + Magento\BraintreeTwo\Model\Ui\ConfigProvider + + + + + + + \Magento\BraintreeTwo\Model\Ui\ConfigProvider::CODE + + + + + + + 1 + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/etc/frontend/routes.xml b/app/code/Magento/BraintreeTwo/etc/frontend/routes.xml new file mode 100644 index 0000000000000..0626cc18a1615 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/frontend/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/BraintreeTwo/etc/module.xml b/app/code/Magento/BraintreeTwo/etc/module.xml new file mode 100644 index 0000000000000..26ac385107f85 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/etc/module.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/registration.php b/app/code/Magento/BraintreeTwo/registration.php new file mode 100644 index 0000000000000..1598b3780f2e9 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/registration.php @@ -0,0 +1,10 @@ + + + + + + + + + + braintreetwo + Magento_BraintreeTwo::form/cc.phtml + + + + + + + diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml b/app/code/Magento/BraintreeTwo/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml new file mode 100644 index 0000000000000..9de9299095b8b --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml @@ -0,0 +1,17 @@ + + + + + + + braintreetwo + Magento_BraintreeTwo::form/cc.phtml + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/BraintreeTwo/view/adminhtml/templates/form/cc.phtml new file mode 100644 index 0000000000000..9e85496ad65c6 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/adminhtml/templates/form/cc.phtml @@ -0,0 +1,69 @@ +escapeHtml($block->getMethodCode()); +$ccType = $block->getInfoData('cc_type'); +?> + diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/templates/payment/script.phtml b/app/code/Magento/BraintreeTwo/view/adminhtml/templates/payment/script.phtml new file mode 100644 index 0000000000000..dc774b0517380 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/adminhtml/templates/payment/script.phtml @@ -0,0 +1,28 @@ +escapeHtml($block->getCode()); + +?> + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/web/css/styles.css b/app/code/Magento/BraintreeTwo/view/adminhtml/web/css/styles.css new file mode 100644 index 0000000000000..d46b52fcf846a --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/adminhtml/web/css/styles.css @@ -0,0 +1,57 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +.control-container { + height: 2.4em; + background-color: #ffffff; + border-radius: 1px; + border: 1px solid #adadad; + color: #303030; + font-size: 1.4rem; + font-weight: 400; + line-height: 1.36; + padding: 0.6rem 1rem 0.6rem; + transition: border-color 0.1s linear; + vertical-align: baseline; +} +.control-container.select-date { + width: 30%; + display: inline-block; +} +.inline-separator { + display: inline-block; + margin: 0 10px; +} +.braintree-hosted-fields-focused { + border-color: #007bdb; +} +.braintree-hosted-fields-invalid{ + border-bottom-color: #D0021B; + border-bottom-width: 2px; +} +.icon-type { + position: absolute; + right: 5px; + top: 26px; + width: 40px; + height: 28px; + background-image: url('../images/cards.png'); + background-position: -1000px 0; + background-repeat: no-repeat; +} +.icon-type-discover { + background-position: 0 0; +} +.icon-type-visa { + background-position: 0 -28px; +} +.icon-type-master-card { + background-position: 0 -56px; +} +.icon-type-maestro { + background-position: 0 -85px; +} +.icon-type-american-express { + background-position: 0 -114px; +} \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/web/images/cards.png b/app/code/Magento/BraintreeTwo/view/adminhtml/web/images/cards.png new file mode 100644 index 0000000000000000000000000000000000000000..866756fe8b9f92b04ab024fb3c91afd4ae9164e4 GIT binary patch literal 5879 zcmbVQcQl+`x1Z68-igE*q9odk(QEWJL`ifpW(K2;P9lgpdJQ3nmgv0&A$p50qC_`J zv?i7;o(2^KI|Tp$pwiM*HN0B+uijWP zlB+MZ=WgoN!sexB>Sctr^YTS_paFMLSX(qm%LQSNHbf&(kK6~*asU9KAI8|!%M`9F zgT%TBBmVFR`?kkTlyq5??HPv%O13)%|-@^f)^^_20G2md8k=4$_kECL4o#p2~85B`@? zrto_p6|4suBrYr=gcOyK1W8H@L&PN|VUmI%s3=5SL{v;fOjHOeCIb_dfk8okKj15E z9w<8*Lsj*^bzPn0!46(tZZaYwzP`S~zGA{y4|@@aw6wH{C{zRr6}sXO@_gj#h42${ z_2m9Xf-2e*>49?uAYAf>MCR+eh4=ah_I-L zi_0Is{^IuZGDQEk8UK;n)A*4aTEr0TiS_nCUgg7%`ycRC?EZH}e;BW<6Xmf%2g1jSTWvOE zT1(w4>7(m`%Ohl!P*2s*BGJrB- zbCVG#D%IE<$+|W#+JSi54Zr~dhcXvT3P5C{^W}S#!@tkktkXyAic2ZX4E#LDx3*4R z2r>sw5dewAkMwG#_ljXVSw3bF2``_+c>_lmOQPaS)r&}orb!5aRWvP?8YKMD;9r4? zf#*3vN){HR!rA7L^UNY$oDa_#Dw}L&6c(%L?u(0`7I7**4eKHLwc5NJfAvi@m=ozWw z=J&S4n2~|u;f`q++3JhmG&Z&K`6+k%!kcDI7u*&t@DNX#OzlZ zB&ThT(<&SeK6gy^2S#Mf40nIq;~{d40eTANO3{9ObdPpdwewY!X|o5DwoU?G<79z6 zSgPLhK!IbS;W`8YIrSJ4=I1324}YGRPE=q*FEZE@mBMs-Jnsa-7%FHiPEJPPf{tj_ z<5+_e6y^Y8gLqQTpy=rqbK?eLQ2%QnP?AA2xf2xInm-I(1;Ios6wVJ7GR< zo>{0Pf8I5m-B>HjJ6VYkUt61-5__`rs_5qV7P`%BILw!C#O*jOTT?f(+c4i&4b5bW zL{>X~9CC1U?Gw2fK;#(-FS|VVw*CDWWoH#eE`|;H(@7yhtA`;)UM)wHpmeEI-|!`=ME&I5cjoT6j&zBuaxyNtRlAS$5x zVr(Mn0swWP9xR}Pk|N&wyz$K>nqCTs+h4I!-i*pfcxkyEaM@hDTn0}*Zw!?OehX}> z`cWtI1z27cJl5Q@=KrKp%u7NVU&QS3}I{2L9b1}Y_w0F8)o%()F1iI7mXLe85IGkmBY|%_i-2J0Cnse_ltIsDoG9o#}K3L0VywP755 zP*=bH>!q!$_U~n=Zg{uZ;^pspBTn=CA3cH5A)>E<6dGljI2bvxib))B(Of^&>2q79 zcjk>e4cOwb`5V3+jA3})Jh(=Rc5m#v=;aQD#`R|a^1{SmUKZXc0@z}%wUj$VTs z268fC%sAqu63)@CG=JL5|43C7) zGjxPj?R8E>KUjdslIja)MJg#>56eI(NaX=0*NYDIw!p?Wm#H?s*~@YJ+xv?D*eg|M z29`S=uecow=w)+@@0)(xg-dC!q**GEJdcHknK4JPfG>PC{theb9&+IJXe3xUR4NE5_-5Z}{nG$2tE@ zqkW5Fb>ojqW0YF_CKVj?yX?|aH#?^GSXf-&KSZ{j-wBM6XRTR1}k!k#pNuc*9?&~v#ZCU|%z}2r8akOPSRaJ5D>Ne&RC)H% zo|Y-9oj6SPsqEIqs%Ze9UaB~9mFg^A(VJ05K2WY^6VRAkE496#cTz@}o>dHhzLk*Y zBOl^yr3}uE_SZ^-!8IPYGU;D_HPq#Pnn5n{>*8XY|CUlRkFB~j)HOqb!pV2WhE3jg z?Bg8I?L*yTT6?j)lJ5Sp5|kF{51znw#oIB1!;#JuBe;);#>w0aKsr-;)khKRQ{6Lf z9~By9s=LrX=Lv;n%v58LeOZhBFuCM5W`e$PYJ1elmm-`G%;?0Q(uIP;4kNYBNphO~ zK9|Q~Z$An%K;TvCu9%Pa_@qG%aUO0df5v$yP)E&yZsgvASF>39Dc@#5M1EZ1?}u6> z_tB;?I3P74hu6Te$^kJQuv1lIRhLaAe_d-rrOJAY$D(D}x0|-BnUuIG|NRM;D)*tW z&5T^rf>{{(7mvUt;r_L1JeX1Th?W+fDB$tKiJ}(I?q>GW0M##XWPhh+vZl-({9aZJ zkru+3r(yp%{9yU5P^sTI-wT74+ufm5PD%I*ySu+K2olweg`x-hOJnB^oZ$Vxn>kk- z7vR+bl~rPZANc|+9W4wd_=MLphr9w5i3UK_v$wO}9Qq~|%H^98Ca{y*Vv_O}8or(y zhZc99P53-~j?;qs!<}hH*jfhX!HpaolUhukW@luEyXvipZRWoBqzy?>yJy%iDv37iwD737HEfZ zhw+EGqt7BtWrOyF-&5RIXub=aA|gL~8J##x#rT-^`a0B_YazhMNF&bFEsQ zhV;f+_3jeT%o$dE&luVjrGClCV^mPW+-jk4tUE5r|8%$y0uPk-BrhFdb4!$qmRGkY z)O^K7y%-v*tzi8DYpatcl{(5>K4U$+n)PHxj<^*1?ImP{ z`Hh5A23>P*;k3&aCfrRbpH1|-WzFF&+vv@dKars;QA6j>JjtG+q{)RY_AbPqt=&3x z+DNS)=qz61*Vx}_f-@>t%Jy4?T6*|OjKt_kle}&eoHB)SG2CJkZ$mw07#3#ad5)~d z!-5L?QlnLb=6HOm30S1=R5#fG-nNktmPXSnvS`VoLh~8%&MA3pjNcQ}YLRc2(Uoxe zQ4vMi1GktFOGwDQ`rr*5u;%^6YUE?qmE|BkaO9DU%&12Dkm0)zpLA>XoE2-HWk`Iw zhGSJ?u5SbRXMNv|05!QXoC+cj*SQ2kFx}>IlwS`jB|h9(zV>9%hTm3PwPq|UTusig z$;!?kp)z0yFlYEERoSPExYLxB(kz6QtZQm}UE@b4kg8fTmnbl-h)_U*C8&OGO{q=j zrM~9G)HRw!gW*ITFQ&~-c~?ppE*lU<8^W$@r8lcHgitgiu0knW+lzg0nOVl-FDm9>bj?!SH;1aIS1x^ z%k&Pyz2ehj<1=9`_vRAY)Ko?Xwld*L(xFA{|?uK z7o?QmS^dBBGh@|=91_!Bn9I^)I&tN{yI+JXLL0}1jec%_NBJkm>O+@c2X);=SU4v7*yp>PO>Q+B#jhQ-o z{QL$!5*yOzrD)IvyrPa9sWvfY(3JcYoxze>>0An`GKf#A^wE1OqaoK=Xpi*&B;{(Z zYd}K1MO zN%T$`pFCsN`WBW)JUVVuCnvD`9NrqdXZ|yhQ4uJHD*jZQ-wQ=m#JN3K@)er9a-k}Dk-1tiq`Rby_l0e zCmYheTg9(Ro;YKUq8qc6ASghNaEy~~?9ZBhh(bS4P%SrGdl*q6=-4%Cq{mn|d-!>Z zl0UhF6w%ROo#c6bO+a%qW`5=!y7Z*H+* zw)i%_R-aOa~#aMY(65u_A09S z!gcU!{I8`Fx#_r;lBJvSBJE2@imYQKEw@3sWB1m_?CSSx9RRjwhDc@4{+`KhZ_UO5 zey0;#BNx6e!}@&@rPLR1`?mYD4BR7u$9HUGQ0wjM z;<`$F%SU3ieoWyN?c)2o9~$R>sG{fMnR+{Wj^e6K_QNzv#_ND;Ne$*!yGxt#Zp%H4 zt)8t++p2fNQN4&{hZWY=Cr#uu%&!n8p$t((d$w0o)*QL8(arlR*MnX17H>?63JgX_ zR*l_dg1dH}qb+@M+GVa)-NdMCmEmFro&qv#O$Cq^N`Penrw#2xWq0mq3%O!nlCSP) zQo$+m^WiP(Ax_IFTBl`yyGJG~L*S`g=0Sb;UMgk}#GR*q> zyIRg3rK@YdDcN+s zMaSFv0QHSO`RKQAow`b%rDQnCuhtgAv1Fx$CSG{WO? zJ}~*k7+%`A+g@nsX9g=kg5C{1>JccWc|8JS5~(qAr${b0zG1=$fC!+SZqxnigk;y>TGJdx0oPcp;p9cMRc0Xc>N8M>I;CE;l62!m{NIt zM0@Q1JH-Kk>jajnziX>_*;v&Fsdu(eIF>fW0R>VUNzG}t7|e+3;1_#V&BmOhgs*(f zl#~o^c3^@nO%G)UNRrnTT + + + + + + + + + + + + + + + + uiComponent + + + + + + + + Magento_BraintreeTwo/js/view/payment/braintree + + + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/frontend/web/css/styles.css b/app/code/Magento/BraintreeTwo/view/frontend/web/css/styles.css new file mode 100644 index 0000000000000..60bcfef412376 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/frontend/web/css/styles.css @@ -0,0 +1,98 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +#braintreetwo_cc_number { + background: #ffffff; + background-clip: padding-box; + border: 1px solid #c2c2c2; + border-radius: 1px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + height: 32px; + line-height: 1.42857143; + padding: 0 9px; + vertical-align: baseline; + width: 225px; + box-sizing: border-box; +} +#braintreetwo_cc_number.braintree-hosted-fields-focused { + border-color: #777; +} +#braintreetwo_cc_number.braintree-hosted-fields-invalid { + border-color: tomato; +} +#braintreetwo_cc_number.braintree-hosted-fields-valid { + border-color: limegreen; +} + +#braintreetwo_expirationMonth { + background: #ffffff; + background-clip: padding-box; + border: 1px solid #c2c2c2; + border-radius: 1px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + height: 32px; + line-height: 1.42857143; + padding: 0 9px; + vertical-align: baseline; + width: 45px; + box-sizing: border-box; +} +#braintreetwo_expirationMonth.braintree-hosted-fields-focused { + border-color: #777; +} +#braintreetwo_expirationMonth.braintree-hosted-fields-invalid { + border-color: tomato; +} +#braintreetwo_expirationMonth.braintree-hosted-fields-valid { + border-color: limegreen; +} +#braintreetwo_expirationYear { + background: #ffffff; + background-clip: padding-box; + border: 1px solid #c2c2c2; + border-radius: 1px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + height: 32px; + line-height: 1.42857143; + padding: 0 9px; + vertical-align: baseline; + width: 60px; + box-sizing: border-box; +} +#braintreetwo_expirationYear.braintree-hosted-fields-focused { + border-color: #777; +} +#braintreetwo_expirationYear.braintree-hosted-fields-invalid { + border-color: tomato; +} +#braintreetwo_expirationYear.braintree-hosted-fields-valid { + border-color: limegreen; +} + +#braintreetwo_cc_cid { + background: #ffffff; + background-clip: padding-box; + border: 1px solid #c2c2c2; + border-radius: 1px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + height: 32px; + line-height: 1.42857143; + padding: 0 9px; + vertical-align: baseline; + width: 55px; + box-sizing: border-box; +} +#braintreetwo_cc_cid.braintree-hosted-fields-focused { + border-color: #777; +} +#braintreetwo_cc_cid.braintree-hosted-fields-invalid { + border-color: tomato; +} +#braintreetwo_cc_cid.braintree-hosted-fields-valid { + border-color: limegreen; +} \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/braintree.js b/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/braintree.js new file mode 100644 index 0000000000000..9331298ac2849 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/braintree.js @@ -0,0 +1,28 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ +define( + [ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' + ], + function ( + Component, + rendererList + ) { + 'use strict'; + + rendererList.push( + { + type: 'braintreetwo', + component: 'Magento_BraintreeTwo/js/view/payment/method-renderer/braintree' + } + ); + + /** Add view logic here if needed */ + return Component.extend({}); + } +); diff --git a/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/method-renderer/braintree.js b/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/method-renderer/braintree.js new file mode 100644 index 0000000000000..70fd0667891b3 --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/frontend/web/js/view/payment/method-renderer/braintree.js @@ -0,0 +1,279 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ +define( + [ + 'jquery', + 'Magento_Payment/js/view/payment/cc-form', + 'Magento_Ui/js/model/messageList', + 'Magento_Checkout/js/model/quote', + 'mage/translate', + 'Magento_BraintreeTwo/js/validator' + ], + function ( + $, + Component, + globalMessageList, + quote, + $t, + validator + ) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_BraintreeTwo/payment/form', + active: false, + scriptLoaded: false, + braintreeClient: null, + paymentMethodNonce: null, + lastBillingAddress: null, + imports: { + onActiveChange: 'active' + } + }, + + /** + * Set list of observable attributes + * @returns {exports.initObservable} + */ + initObservable: function () { + validator.setConfig(window.checkoutConfig.payment[this.getCode()]); + + this._super() + .observe('active scriptLoaded'); + + return this; + }, + + /** + * Get payment name + * @returns {String} + */ + getCode: function () { + return 'braintreetwo'; + }, + + /** + * Get full selector name + * @param {String} field + * @returns {String} + */ + getSelector: function (field) { + return '#' + this.getCode() + '_' + field; + }, + + /** + * Check if payment is active + * @returns {Boolean} + */ + isActive: function () { + var active = this.getCode() === this.isChecked(); + + this.active(active); + + return active; + }, + + /** + * Triggers on payment change + * @param {Boolean} isActive + */ + onActiveChange: function (isActive) { + if (!isActive) { + return; + } + + if (this.getClientToken()) { + if (!this.scriptLoaded()) { + this.loadScript(); + } + } else { + globalMessageList.addErrorMessage({ + 'message': $t('Sorry, but something went wrong') + }); + } + }, + + /** + * Load Braintree SDK + */ + loadScript: function () { + var state = this.scriptLoaded, + self = this; + + $('body').trigger('processStart'); + require([this.getSdkUrl()], function (braintree) { + state(true); + self.braintreeClient = braintree; + self.initBraintree(); + $('body').trigger('processStop'); + }); + }, + + /** + * Init Braintree client + */ + initBraintree: function () { + var self = this, + fields = { + number: { + selector: self.getSelector('cc_number') + }, + expirationMonth: { + selector: self.getSelector('expirationMonth'), + placeholder: $t('MM') + }, + expirationYear: { + selector: self.getSelector('expirationYear'), + placeholder: $t('YYYY') + }, + + /** + * Triggers on Hosted Field changes + * @param {Object} event + * @returns {Boolean} + */ + onFieldEvent: function (event) { + if (event.isEmpty === false) { + self.validateCardType(); + } + + if (event.type === 'fieldStateChange') { + // Handle a change in validation or card type + self.selectedCardType(null); + + if (!event.isPotentiallyValid && !event.isValid) { + return false; + } + + if (event.card) { + self.selectedCardType( + validator.getMageCardType(event.card.type, self.getCcAvailableTypes()) + ); + } + } + } + }; + + if (self.hasVerification()) { + fields.cvv = { + selector: self.getSelector('cc_cid') + }; + } + + this.braintreeClient.setup(this.getClientToken(), 'custom', { + id: 'co-transparent-form-braintree', + hostedFields: fields, + + /** + * Triggers on payment nonce receive + * @param {Object} response + */ + onPaymentMethodReceived: function (response) { + self.paymentMethodNonce = response.nonce; + self.placeOrder(); + }, + + /** + * Triggers on any Braintree error + * @param {Object} response + */ + onError: function (response) { + self.messageContainer.addErrorMessage({ + 'message': response.message + }); + } + }); + }, + + /** + * Validate current credit card type + * @returns {Boolean} + */ + validateCardType: function () { + if (this.selectedCardType() === null) { + $(this.getSelector('cc_number')).attr('class', 'braintree-hosted-fields-invalid'); + + return false; + } + + return true; + }, + + /** + * Get url of Braintree SDK + * @returns {String} + */ + getSdkUrl: function () { + + return window.checkoutConfig.payment[this.getCode()].sdkUrl; + }, + + /** + * Get client token + * @returns {String|*} + */ + getClientToken: function () { + + return window.checkoutConfig.payment[this.getCode()].clientToken; + }, + + /** + * Get list of available CC types + */ + getCcAvailableTypes: function () { + var availableTypes = validator.getAvailableCardTypes(), + billingAddress = quote.billingAddress(), + billingCountryId; + + this.lastBillingAddress = quote.shippingAddress(); + + if (!billingAddress) { + billingAddress = this.lastBillingAddress; + } + + billingCountryId = billingAddress.countryId; + + if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { + + return validator.collectTypes( + availableTypes, validator.getCountrySpecificCardTypes(billingCountryId) + ); + } + + return availableTypes; + }, + + /** + * Get data + * @returns {Object} + */ + getData: function () { + return { + 'method': this.item.method, + 'additional_data': { + 'payment_method_nonce': this.paymentMethodNonce + } + }; + }, + + /** + * Trigger order placing + */ + placeOrderClick: function () { + if (this.validateCardType()) { + $(this.getSelector('submit')).trigger('click'); + } else { + this.messageContainer.addErrorMessage({ + 'message': $t('Please enter a valid credit card type number') + }); + } + } + + }); + } +); diff --git a/app/code/Magento/BraintreeTwo/view/frontend/web/template/payment/form.html b/app/code/Magento/BraintreeTwo/view/frontend/web/template/payment/form.html new file mode 100644 index 0000000000000..0181d7a4cd18e --- /dev/null +++ b/app/code/Magento/BraintreeTwo/view/frontend/web/template/payment/form.html @@ -0,0 +1,123 @@ + +
+
+ + +
+
+ + + +
+ + + +
+
+
+ + + +
+
+
+
    + +
  • + + + +
  • + +
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+ + + +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/composer.json b/composer.json index 700c9de61aeaf..84804552a9485 100644 --- a/composer.json +++ b/composer.json @@ -83,6 +83,7 @@ "magento/module-backend": "100.0.2", "magento/module-backup": "100.0.2", "magento/module-braintree": "100.0.2", + "magento/module-braintree-two": "100.0.2", "magento/module-bundle": "100.0.2", "magento/module-bundle-import-export": "100.0.2", "magento/module-cache-invalidate": "100.0.2", From d16d8292de03beb3c2dcfb3235d399bbf4767b9d Mon Sep 17 00:00:00 2001 From: Ievgen Sentiabov Date: Mon, 30 Nov 2015 10:44:29 +0200 Subject: [PATCH 20/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Changed event triggering to direct function call --- .../Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/code/Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js b/app/code/Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js index a5337a487ca33..57586aaf41693 100644 --- a/app/code/Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js +++ b/app/code/Magento/BraintreeTwo/view/adminhtml/web/js/braintree.js @@ -49,9 +49,6 @@ define([ // re-init payment method events self.$selector.off('changePaymentMethod.' + this.code) .on('changePaymentMethod.' + this.code, this.changePaymentMethod.bind(this)); - $('#' + self.container).on('initBraintree', function () { - self.initBraintree(); - }); // listen block changes domObserver.get('#' + self.container, function () { @@ -112,7 +109,7 @@ define([ require([this.sdkUrl], function (braintree) { state(true); self.braintree = braintree; - $('#' + self.container).trigger('initBraintree'); + self.initBraintree(); $('body').trigger('processStop'); }); }, From f954f57cd8dabe807a82934f66fb2962e14013ca Mon Sep 17 00:00:00 2001 From: rliukshyn Date: Mon, 30 Nov 2015 12:21:36 +0200 Subject: [PATCH 21/79] MAGETWO-45682: Create automated functional tests for Braintree --- .../Magento/Braintree/Test/Block/Form/Cc.php | 61 +++++++++++++++++++ .../Magento/Braintree/Test/Block/Form/Cc.xml | 20 ++++++ .../Test/Fixture/CreditCardBraintree.xml | 19 ++++++ .../Braintree/Test/Page/CheckoutOnepage.xml | 12 ++++ .../Braintree/Test/Repository/ConfigData.xml | 57 +++++++++++++++++ .../Braintree/Test/Repository/CreditCard.xml | 16 +++++ .../Test/TestCase/OnePageCheckoutTest.xml | 30 +++++++++ .../Checkout/Test/Block/Onepage/Payment.php | 10 ++- .../Test/TestStep/SelectPaymentMethodStep.php | 11 +++- 9 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Page/CheckoutOnepage.xml create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml create mode 100644 dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php new file mode 100644 index 0000000000000..cd933f3993cc0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php @@ -0,0 +1,61 @@ + "#braintree-hosted-field-number", + "expiration" => "#braintree-hosted-field-expirationDate", + "cvv" => "#braintree-hosted-field-cvv", + ]; + + /** + * Cc constructor. + * @param SimpleElement $element + * @param BlockFactory $blockFactory + * @param Mapper $mapper + * @param BrowserInterface $browser + */ + public function __construct( + SimpleElement $element, + BlockFactory $blockFactory, + Mapper $mapper, + BrowserInterface $browser + ) { + parent::__construct($element, $blockFactory, $mapper, $browser); + } + + public function fill(FixtureInterface $fixture, SimpleElement $element = null) + { + $mapping = $this->dataMapping($fixture->getData()); + foreach ($this->braintreeForm as $field => $iframe) { + $this->browser->switchToFrame(new Locator($iframe)); + $element = $this->browser->find('body'); + $this->_fill([$mapping[$field]], $element); + $this->browser->switchToFrame(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml new file mode 100644 index 0000000000000..394609e2ca7c1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml @@ -0,0 +1,20 @@ + + + + + + #credit-card-number + + + #expiration + + + #cvv + + + \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml new file mode 100644 index 0000000000000..42bc599eea401 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/CheckoutOnepage.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/CheckoutOnepage.xml new file mode 100644 index 0000000000000..4b885217858a1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/CheckoutOnepage.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml new file mode 100644 index 0000000000000..86c039151d322 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml @@ -0,0 +1,57 @@ + + + + + + + payment + 1 + Yes + PAYMENT_BRAINTREETWO_MERCHANT_ACCOUNT_ID + + + payment + 1 + + PAYMENT_BRAINTREETWO_MERCHANT_ID + + + payment + 1 + + PAYMENT_PAYMENT_BRAINTREETWO_PUBLIC_KEY + + + payment + 1 + + PAYMENT_BRAINTREETWO_PRIVATE_KEY + + + payment + 1 + Yes + 1 + + + payment + 1 + Yes + 1 + + + + + payment + 1 + Yes + 0 + + + + diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml new file mode 100644 index 0000000000000..bf759b37a45b5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml @@ -0,0 +1,16 @@ + + + + + + 4111111111111111 + 01/2020 + 123 + + + diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml new file mode 100644 index 0000000000000..367d2ada4d898 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml @@ -0,0 +1,30 @@ + + + + + + catalogProductSimple::product_10_dollar + default + US_address_1 + guest + Flat Rate + Fixed + + 15.00 + + braintreetwo + credit_card_braintree + visa_braintree + braintreetwo + test_type:3rd_party_test + + + + + + diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php index 744e5d27fad77..d7ee7aecd5cdb 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php @@ -7,7 +7,9 @@ namespace Magento\Checkout\Test\Block\Onepage; use Magento\Mtf\Block\Block; +use Magento\Mtf\Fixture\InjectableFixture; use Magento\Payment\Test\Fixture\CreditCard; +use Zend\Server\Reflection\ReflectionClass; /** * Checkout payment block. @@ -75,11 +77,11 @@ class Payment extends Block * Select payment method. * * @param array $payment - * @param CreditCard|null $creditCard + * @param InjectableFixture|null $creditCard * @throws \Exception * @return void */ - public function selectPaymentMethod(array $payment, CreditCard $creditCard = null) + public function selectPaymentMethod(array $payment, InjectableFixture $creditCard = null) { $paymentSelector = sprintf($this->paymentMethodInput, $payment['method']); $paymentLabelSelector = sprintf($this->paymentMethodLabel, $payment['method']); @@ -99,9 +101,11 @@ public function selectPaymentMethod(array $payment, CreditCard $creditCard = nul $this->_rootElement->find($this->purchaseOrderNumber)->setValue($payment['po_number']); } if ($creditCard !== null) { + $class = explode('\\', get_class($creditCard)); + $module = $class[1]; /** @var \Magento\Payment\Test\Block\Form\Cc $formBlock */ $formBlock = $this->blockFactory->create( - '\\Magento\\Payment\\Test\\Block\\Form\\Cc', + "\\Magento\\{$module}\\Test\\Block\\Form\\Cc", ['element' => $this->_rootElement->find('#payment_form_' . $payment['method'])] ); $formBlock->fill($creditCard); diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectPaymentMethodStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectPaymentMethodStep.php index b0674973ad2ce..309a747c3a4d4 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectPaymentMethodStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectPaymentMethodStep.php @@ -7,6 +7,7 @@ namespace Magento\Checkout\Test\TestStep; use Magento\Checkout\Test\Page\CheckoutOnepage; +use Magento\Mtf\Fixture\FixtureFactory; use Magento\Mtf\TestStep\TestStepInterface; use Magento\Payment\Test\Fixture\CreditCard; @@ -41,16 +42,20 @@ class SelectPaymentMethodStep implements TestStepInterface * @constructor * @param CheckoutOnepage $checkoutOnepage * @param array $payment - * @param CreditCard|null $creditCard + * @param FixtureFactory $fixtureFactory + * @param string $creditCardClass + * @param array|CreditCard|null $creditCard */ public function __construct( CheckoutOnepage $checkoutOnepage, array $payment, - CreditCard $creditCard = null + FixtureFactory $fixtureFactory, + $creditCardClass = 'credit_card', + array $creditCard = [] ) { $this->checkoutOnepage = $checkoutOnepage; $this->payment = $payment; - $this->creditCard = $creditCard; + $this->creditCard = $fixtureFactory->createByCode($creditCardClass, ['dataset' => $creditCard['dataset']]); } /** From f4431da28fb21863880966c6365164d6326a222b Mon Sep 17 00:00:00 2001 From: Evgeniy Kolesov Date: Mon, 30 Nov 2015 13:44:56 +0200 Subject: [PATCH 22/79] MAGETWO-43412: [UI] Create collapsible component for forms --- .../Ui/view/base/web/templates/form/fieldset.html | 1 - .../web/css/source/module/main/_collapsible-blocks.less | 2 -- .../web/css/source/module/main/_page-nav.less | 9 +++++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html index e822098b248b3..df5ce9a6e5203 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/fieldset.html @@ -17,7 +17,6 @@ '_loading': loading, '_error': error }"> - data-bind="click: toggleOpened, keyboard: { 13: toggleOpened }"> diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less index 97602ba06a235..f98afc39cdf9c 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less @@ -209,7 +209,6 @@ .admin__collapsible-block { .comment { - // ToDo UI: rename to .collapsible-text .__collapsible-text-pattern(); } @@ -225,7 +224,6 @@ } ~ .admin__collapsible-block { - // ToDo UI: rename to .collapsible-content .__collapsible-content-pattern(); } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less index 138868bd71474..604bc2612c323 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less @@ -34,10 +34,11 @@ @admin__page-nav-icon-error__color: @color-phoenix; @admin__page-nav-item-message-loader__font-size: 2rem; +@admin__page-nav-tooltip-message__width: 27rem; @admin__page-nav-tooltip__background: @admin__page-nav__background-color; +@admin__page-nav-tooltip__border-color: @color-gray75; @admin__page-nav-tooltip__box-shadow: 0 3px 9px 0 rgba(0, 0, 0, .3); @admin__page-nav-tooltip__z-index1: @field-tooltip__z-index; -@admin__page-nav-tooltip__border-color: @color-gray75; @admin__page-nav-transition: border-color .1s ease-out, background-color .1s ease-out; @@ -226,9 +227,10 @@ padding: 1.5rem; position: absolute; text-transform: none; - width: 27rem; + width: @admin__page-nav-tooltip-message__width; word-break: normal; z-index: 2; + &:after, &:before { .lib-arrow( @@ -243,11 +245,13 @@ top: 100%; z-index: 3; } + &:after { border-top-color: @admin__page-nav-tooltip__background; margin-top: -1px; z-index: 4; } + &:before { border-top-color: @admin__page-nav-tooltip__border-color; margin-top: 1px; @@ -294,6 +298,7 @@ font-size: @admin__page-nav-title__font-size; padding-left: .8em; vertical-align: baseline; + &:after { color: @admin__page-nav-link__changed__color; content: @admin__page-nav-icon-changed__content; From 259040219ac4b1649cb74a298473098fcb08060d Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Mon, 30 Nov 2015 15:43:35 +0200 Subject: [PATCH 23/79] MAGETWO-46063: [Dev Experience] Checkout Agreements resource model data inconsistency --- .../Block/Adminhtml/Agreement/Edit/Form.php | 4 ++-- .../Block/Adminhtml/Agreement/Grid.php | 4 ++-- .../Model/CheckoutAgreementsRepository.php | 2 +- .../Model/ResourceModel/Agreement.php | 21 ++++++++++++------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Edit/Form.php b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Edit/Form.php index 104c89f822765..24a48e097b867 100644 --- a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Edit/Form.php +++ b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Edit/Form.php @@ -121,7 +121,7 @@ protected function _prepareForm() if (!$this->_storeManager->isSingleStoreMode()) { $field = $fieldset->addField( - 'store_id', + 'stores', 'multiselect', [ 'name' => 'stores[]', @@ -137,7 +137,7 @@ protected function _prepareForm() $field->setRenderer($renderer); } else { $fieldset->addField( - 'store_id', + 'stores', 'hidden', ['name' => 'stores[]', 'value' => $this->_storeManager->getStore(true)->getId()] ); diff --git a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php index 8e4694266cb22..a3249ca8b127e 100644 --- a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php +++ b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php @@ -78,10 +78,10 @@ protected function _prepareColumns() if (!$this->_storeManager->isSingleStoreMode()) { $this->addColumn( - 'store_id', + 'stores', [ 'header' => __('Store View'), - 'index' => 'store_id', + 'index' => 'stores', 'type' => 'store', 'store_all' => true, 'store_view' => true, diff --git a/app/code/Magento/CheckoutAgreements/Model/CheckoutAgreementsRepository.php b/app/code/Magento/CheckoutAgreements/Model/CheckoutAgreementsRepository.php index 01653546d38bc..6f920d9ee3374 100644 --- a/app/code/Magento/CheckoutAgreements/Model/CheckoutAgreementsRepository.php +++ b/app/code/Magento/CheckoutAgreements/Model/CheckoutAgreementsRepository.php @@ -124,7 +124,7 @@ public function save(\Magento\CheckoutAgreements\Api\Data\AgreementInterface $da if ($storeId === null) { $storeId = $this->storeManager->getStore()->getId(); } - $data->setStores($storeId); + $data->setStores([$storeId]); try { $this->resourceModel->save($data); } catch (\Exception $e) { diff --git a/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement.php b/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement.php index 1be4a14823274..bedd5838a84d0 100644 --- a/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement.php +++ b/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement.php @@ -70,13 +70,16 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) */ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) { - $condition = ['agreement_id = ?' => $object->getId()]; - $this->getConnection()->delete($this->getTable('checkout_agreement_store'), $condition); + $this->getConnection()->delete( + $this->getTable('checkout_agreement_store'), + ['agreement_id = ?' => $object->getId()] + ); - foreach ((array)$object->getData('stores') as $store) { - $storeArray = []; - $storeArray['agreement_id'] = $object->getId(); - $storeArray['store_id'] = $store; + foreach ((array)$object->getData('stores') as $storeId) { + $storeArray = [ + 'agreement_id' => $object->getId(), + 'store_id' => $storeId + ]; $this->getConnection()->insert($this->getTable('checkout_agreement_store'), $storeArray); } @@ -96,8 +99,10 @@ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object) ->from($this->getTable('checkout_agreement_store'), ['store_id']) ->where('agreement_id = :agreement_id'); - if ($stores = $this->getConnection()->fetchCol($select, [':agreement_id' => $object->getId()])) { - $object->setData('store_id', $stores); + $stores = $this->getConnection()->fetchCol($select, [':agreement_id' => $object->getId()]); + + if ($stores) { + $object->setData('stores', $stores); } return parent::_afterLoad($object); From c9ed55393725666c61b1acd5757ef8ea9b39fc0a Mon Sep 17 00:00:00 2001 From: Alex Akimov Date: Mon, 30 Nov 2015 17:53:12 +0200 Subject: [PATCH 24/79] MAGETWO-46091: Estimate-shipping-methods Service is Unavailable for Admin --- app/code/Magento/Quote/etc/webapi.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/code/Magento/Quote/etc/webapi.xml b/app/code/Magento/Quote/etc/webapi.xml index 8dac619706594..bb64e240f6d3b 100644 --- a/app/code/Magento/Quote/etc/webapi.xml +++ b/app/code/Magento/Quote/etc/webapi.xml @@ -104,6 +104,18 @@ + + + + + + + + + + + + From 4543b96a24cab2f18daf92540aa0cdcd66017d68 Mon Sep 17 00:00:00 2001 From: "Yushkin, Dmytro" Date: Mon, 30 Nov 2015 19:01:50 +0200 Subject: [PATCH 25/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Change convertation method object to array via type casting in HTTP/Client - Remove unnessessary constant and variable in response handler --- .../BraintreeTwo/Gateway/Http/Client/TransactionSale.php | 2 +- .../BraintreeTwo/Gateway/Response/CardDetailsHandler.php | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php index b2c39ebfb632a..ed633f8ae5215 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php @@ -57,7 +57,7 @@ public function placeRequest(TransferInterface $transferObject) } catch (\Exception $e) { throw new ClientException(__($e->getMessage())); } finally { - $log['response'] = json_decode(json_encode($response['object']), true); + $log['response'] = (array) $response['object']; $this->logger->debug($log); } diff --git a/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php index bb69ed079668d..f9c8133dccd92 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php @@ -19,8 +19,6 @@ */ class CardDetailsHandler implements HandlerInterface { - const CARD_DETAILS = 'creditCard'; - const CARD_TYPE = 'cardType'; const CARD_EXP_MONTH = 'expirationMonth'; @@ -56,8 +54,7 @@ public function handle(array $handlingSubject, array $response) $payment = $details->getPayment(); ContextHelper::assertOrderPayment($payment); - $card = self::CARD_DETAILS; - $creditCard = $transaction->$card; + $creditCard = $transaction->creditCard; $payment->setCcLast4($creditCard[self::CARD_LAST4]); $payment->setCcExpMonth($creditCard[self::CARD_EXP_MONTH]); $payment->setCcExpYear($creditCard[self::CARD_EXP_YEAR]); From d867ef9b78427e6f6f1e2ef8d95b74f21ca90b6c Mon Sep 17 00:00:00 2001 From: Serhiy Shkolyarenko Date: Tue, 1 Dec 2015 10:49:49 +0200 Subject: [PATCH 26/79] MAGETWO-44852: [Folks] TAF activity S77 added constraint for minicart --- .../Test/TestCase/OnePageCheckoutTest.xml | 1 + .../Checkout/Test/Block/Cart/Sidebar.php | 15 +++++++ .../Test/Constraint/AssertMinicartEmpty.php | 40 +++++++++++++++++++ .../Test/TestCase/OnePageCheckoutTest.xml | 5 +++ .../Dhl/Test/TestCase/OnePageCheckoutTest.xml | 1 + .../Test/TestCase/OnePageCheckoutTest.xml | 2 + .../TestCase/CheckoutWithGiftMessagesTest.xml | 2 + .../ExpressCheckoutFromProductPageTest.xml | 1 + .../ExpressCheckoutFromShoppingCartTest.xml | 1 + .../TestCase/ExpressCheckoutOnePageTest.xml | 3 ++ .../Ups/Test/TestCase/OnePageCheckoutTest.xml | 2 + .../Test/TestCase/OnePageCheckoutTest.xml | 2 + 12 files changed, 75 insertions(+) create mode 100644 dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php diff --git a/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml index b41addebd2c62..545a21785129e 100644 --- a/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml @@ -23,6 +23,7 @@ authorizenet test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php index 04f4d4569f94a..6d6bd9e9927d4 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php @@ -29,6 +29,11 @@ class Sidebar extends Block */ protected $cartLink = 'a.showcart'; + /** + * @var string + */ + protected $productCounter = '//*[@data-block="minicart"]//*[@class="counter-number"]'; + /** * Mini cart content selector. * @@ -101,6 +106,16 @@ function () use ($browser, $selector) { ); } + /** + * Get cart items quantity + * + * @return string + */ + public function isItemsQtyVisible() + { + return $this->_rootElement->find($this->productCounter, Locator::SELECTOR_XPATH)->isVisible(); + } + /** * Get product quantity. * diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php new file mode 100644 index 0000000000000..7d5a44a7720fb --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php @@ -0,0 +1,40 @@ +getCartSidebarBlock()->isItemsQtyVisible(), + 'Minicart is not empty' + ); + } + + /** + * Returns a string representation of the object + * + * @return string + */ + public function toString() + { + return 'Minicart emptiness check'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 0b86f927eef12..8ba8208a18aa8 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -20,6 +20,7 @@ Back, Send Email, Cancel, Hold, Invoice, Edit checkmo_specificcountry_gb + @@ -40,6 +41,7 @@ Back, Send Email, Cancel, Hold, Ship, Invoice, Edit banktransfer + @@ -59,6 +61,7 @@ Back, Send Email, Cancel, Hold, Ship, Invoice, Edit banktransfer_specificcountry_gb + @@ -81,6 +84,7 @@ test_type:acceptance_test checkmo + @@ -102,6 +106,7 @@ checkmo, freeshipping_minimum_order_amount_100 test_type:acceptance_test + diff --git a/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml index 5b8bedaf9b977..6c921d21a2e5c 100644 --- a/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml @@ -20,6 +20,7 @@ checkmo, dhl_eu, shipping_origin_CH, config_base_currency_ch test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml index 580de1cac6f91..92b074830b9f7 100644 --- a/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml @@ -20,6 +20,7 @@ checkmo, fedex, shipping_origin_US_CA test_type:3rd_party_test + @@ -36,6 +37,7 @@ checkmo, fedex, shipping_origin_US_CA test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/TestCase/CheckoutWithGiftMessagesTest.xml b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/TestCase/CheckoutWithGiftMessagesTest.xml index d91f2b5a10140..337d4a9fbe2aa 100644 --- a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/TestCase/CheckoutWithGiftMessagesTest.xml +++ b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/TestCase/CheckoutWithGiftMessagesTest.xml @@ -22,6 +22,7 @@ text_gift_message cashondelivery + @@ -40,6 +41,7 @@ default cashondelivery + diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml index f54b7ec49b63a..83dfe5e439a23 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml @@ -29,6 +29,7 @@ paypal_express, freeshipping test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml index ff3026f814cbe..f47a4c9635a1d 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml @@ -28,6 +28,7 @@ payflowpro test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml index b879a84567a1c..8677d2de93c2b 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml @@ -30,6 +30,7 @@ paypal_express test_type:3rd_party_test + @@ -57,6 +58,7 @@ payflowlink test_type:3rd_party_test + @@ -84,6 +86,7 @@ paypal_express test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml index d739d5bae2626..31a1bc9a90a1e 100644 --- a/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml @@ -19,6 +19,7 @@ checkmo, ups, shipping_origin_US_CA test_type:3rd_party_test + @@ -35,6 +36,7 @@ checkmo, ups, shipping_origin_US_CA test_type:3rd_party_test + diff --git a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml index cf2ad50688a9e..e89cd863ae4aa 100644 --- a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml @@ -19,6 +19,7 @@ checkmo, usps, shipping_origin_US_CA test_type:3rd_party_test + @@ -35,6 +36,7 @@ checkmo, usps, shipping_origin_US_CA test_type:3rd_party_test + From 80bf1bbcf835db08a02afcd9648192bf61054206 Mon Sep 17 00:00:00 2001 From: Ievgen Sentiabov Date: Tue, 1 Dec 2015 10:56:04 +0200 Subject: [PATCH 27/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Refactored code related to code review - Added event triggering for single configured payment on Backend - Added check for empty Braintree response --- app/code/Magento/BraintreeTwo/Block/Form.php | 53 ++++++++++++++----- .../Gateway/Http/Client/TransactionSale.php | 4 +- .../Magento/BraintreeTwo/Helper/Country.php | 10 +++- .../Model/Adminhtml/Source/CcType.php | 31 +++++++++-- .../Model/Adminhtml/System/Config/Country.php | 36 +++++++++++-- .../Observer/DataAssignObserver.php | 4 +- .../BraintreeTwo/Test/Unit/Block/FormTest.php | 33 +++++++----- .../Test/Unit/Helper/CountryTest.php | 2 +- .../Adminhtml/System/Config/CountryTest.php | 1 - .../BraintreeTwo/etc/adminhtml/system.xml | 1 + app/code/Magento/BraintreeTwo/etc/config.xml | 4 +- .../order/create/billing/method/form.phtml | 14 +++-- 12 files changed, 146 insertions(+), 47 deletions(-) diff --git a/app/code/Magento/BraintreeTwo/Block/Form.php b/app/code/Magento/BraintreeTwo/Block/Form.php index 9a716f8a87a9f..dc46508a1c6d8 100644 --- a/app/code/Magento/BraintreeTwo/Block/Form.php +++ b/app/code/Magento/BraintreeTwo/Block/Form.php @@ -10,6 +10,7 @@ use Magento\Payment\Block\Form\Cc; use Magento\Payment\Model\Config; use Magento\BraintreeTwo\Gateway\Config\Config as GatewayConfig; +use Magento\BraintreeTwo\Model\Adminhtml\Source\CcType; /** * Class Form @@ -28,6 +29,11 @@ class Form extends Cc */ protected $gatewayConfig; + /** + * @var \Magento\BraintreeTwo\Model\Adminhtml\Source\CcType + */ + protected $ccType; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Payment\Model\Config $paymentConfig @@ -40,11 +46,13 @@ public function __construct( Config $paymentConfig, Quote $sessionQuote, GatewayConfig $gatewayConfig, + CcType $ccType, array $data = [] ) { parent::__construct($context, $paymentConfig, $data); $this->sessionQuote = $sessionQuote; $this->gatewayConfig = $gatewayConfig; + $this->ccType = $ccType; } /** @@ -53,20 +61,9 @@ public function __construct( */ public function getCcAvailableTypes() { - $types = $this->_paymentConfig->getCcTypes(); - - // get only available for Braintree card types - $configCardTypes = array_fill_keys($this->gatewayConfig->getCcAvailableCardTypes(), ''); - $filteredTypes = array_intersect_key($types, $configCardTypes); - + $configuredCardTypes = $this->getConfiguredCardTypes(); $countryId = $this->sessionQuote->getQuote()->getBillingAddress()->getCountryId(); - $availableTypes = $this->gatewayConfig->getCountryAvailableCardTypes($countryId); - // filter card types only if specific card types are set for country - if (!empty($availableTypes)) { - $availableTypes = array_fill_keys($availableTypes, ''); - $filteredTypes = array_intersect_key($filteredTypes, $availableTypes); - } - return $filteredTypes; + return $this->filterCardTypesForCountry($configuredCardTypes, $countryId); } /** @@ -77,4 +74,34 @@ public function useCvv() { return $this->gatewayConfig->useCvv(); } + + /** + * Get card types available for Braintree + * @return array + */ + private function getConfiguredCardTypes() + { + $types = $this->ccType->getCcTypes(); + $configCardTypes = array_fill_keys($this->gatewayConfig->getCcAvailableCardTypes(), ''); + + return array_intersect_key($types, $configCardTypes); + } + + /** + * Filter card types for specific country + * @param array $configCardTypes + * @param string $countryId + * @return array + */ + private function filterCardTypesForCountry(array $configCardTypes, $countryId) + { + $filtered = $configCardTypes; + $countryCardTypes = $this->gatewayConfig->getCountryAvailableCardTypes($countryId); + // filter card types only if specific card types are set for country + if (!empty($countryCardTypes)) { + $availableTypes = array_fill_keys($countryCardTypes, ''); + $filtered = array_intersect_key($filtered, $availableTypes); + } + return $filtered; + } } diff --git a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php index b2c39ebfb632a..c5802d09d75b9 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php @@ -55,7 +55,9 @@ public function placeRequest(TransferInterface $transferObject) try { $response['object'] = $this->braintreeTransaction->sale($data); } catch (\Exception $e) { - throw new ClientException(__($e->getMessage())); + throw new ClientException(__( + $e->getMessage() ?: 'Transaction has been declined, please, try again later.' + )); } finally { $log['response'] = json_decode(json_encode($response['object']), true); $this->logger->debug($log); diff --git a/app/code/Magento/BraintreeTwo/Helper/Country.php b/app/code/Magento/BraintreeTwo/Helper/Country.php index 88a81e499caea..054a29d9b7c24 100644 --- a/app/code/Magento/BraintreeTwo/Helper/Country.php +++ b/app/code/Magento/BraintreeTwo/Helper/Country.php @@ -19,6 +19,11 @@ class Country */ private $collectionFactory; + /** + * @var \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country + */ + private $countryConfig; + /** * @var array */ @@ -27,9 +32,10 @@ class Country /** * @param CollectionFactory $factory */ - public function __construct(CollectionFactory $factory) + public function __construct(CollectionFactory $factory, CountryConfig $countryConfig) { $this->collectionFactory = $factory; + $this->countryConfig = $countryConfig; } /** @@ -41,7 +47,7 @@ public function getCountries() { if (!$this->countries) { $this->countries = $this->collectionFactory->create() - ->addFieldToFilter('country_id', ['nin' => CountryConfig::$excludedCountries]) + ->addFieldToFilter('country_id', ['nin' => $this->countryConfig->getExcludedCountries()]) ->loadData() ->toOptionArray(false); } diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php index 0f5b63ae28706..d5119ffa9e647 100644 --- a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php @@ -13,6 +13,14 @@ */ class CcType extends PaymentCctype { + /** + * List of specific credit card types + * @var array + */ + private $mapper = [ + 'CUP' => 'China Union Pay' + ]; + /** * Allowed credit card types * @@ -20,16 +28,33 @@ class CcType extends PaymentCctype */ public function getAllowedTypes() { - return ['VI', 'MC', 'AE', 'DI', 'JCB', 'OT']; + return ['VI', 'MC', 'AE', 'DI', 'JCB', 'MI', 'DN', 'CUP', 'OT']; } /** - * Geting credit cards types + * Getting credit cards types * * @return array */ public function getCcTypes() { - return $this->_paymentConfig->getCcTypes(); + return array_merge($this->mapper, $this->_paymentConfig->getCcTypes()); + } + + /** + * @inheritdoc + */ + public function toOptionArray() + { + $allowed = $this->getAllowedTypes(); + $options = []; + + foreach ($this->getCcTypes() as $code => $name) { + if (in_array($code, $allowed)) { + $options[] = ['value' => $code, 'label' => $name]; + } + } + + return $options; } } diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php index 65cd8f9e6665b..9bb3a146fcf82 100644 --- a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/Country.php @@ -29,8 +29,26 @@ class Country implements ArrayInterface /** * Countries not supported by Braintree */ - public static $excludedCountries = [ - 'MM', 'IR', 'SD', 'BY', 'CI', 'CD', 'CG', 'IQ', 'LR', 'LB', 'KP', 'SL', 'SY', 'ZW', 'AL', 'BA', 'MK', 'ME', 'RS' + protected $excludedCountries = [ + 'MM', + 'IR', + 'SD', + 'BY', + 'CI', + 'CD', + 'CG', + 'IQ', + 'LR', + 'LB', + 'KP', + 'SL', + 'SY', + 'ZW', + 'AL', + 'BA', + 'MK', + 'ME', + 'RS' ]; /** @@ -49,7 +67,7 @@ public function toOptionArray($isMultiselect = false) { if (!$this->options) { $this->options = $this->countryCollection - ->addFieldToFilter('country_id', ['nin' => self::$excludedCountries]) + ->addFieldToFilter('country_id', ['nin' => $this->getExcludedCountries()]) ->loadData() ->toOptionArray(false); } @@ -70,7 +88,15 @@ public function toOptionArray($isMultiselect = false) */ public function isCountryRestricted($countryId) { - $keys = array_flip(self::$excludedCountries); - return isset($keys[$countryId]); + return in_array($countryId, $this->getExcludedCountries()); + } + + /** + * Return list of excluded countries + * @return array + */ + public function getExcludedCountries() + { + return $this->excludedCountries; } } diff --git a/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php index 1905de8c79987..8b779600fdeab 100644 --- a/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php +++ b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php @@ -24,10 +24,10 @@ public function execute(Observer $observer) $method = $this->readMethodArgument($observer); $data = $this->readDataArgument($observer); $paymentInfo = $method->getInfoInstance(); - if ($data->getDataByKey('payment_method_nonce') !== null) { + if ($data->getDataByKey(self::PAYMENT_METHOD_NONCE) !== null) { $paymentInfo->setAdditionalInformation( self::PAYMENT_METHOD_NONCE, - $data->getDataByKey('payment_method_nonce') + $data->getDataByKey(self::PAYMENT_METHOD_NONCE) ); } } diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php index ce0e6d4535966..2f41f3556cd9b 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php @@ -8,9 +8,14 @@ use Magento\Backend\Model\Session\Quote; use Magento\BraintreeTwo\Block\Form; use Magento\BraintreeTwo\Gateway\Config\Config as GatewayConfig; +use Magento\BraintreeTwo\Model\Adminhtml\Source\CcType; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Payment\Model\Config; +/** + * Class FormTest + * @package Magento\BraintreeTwo\Test\Unit\Block + */ class FormTest extends \PHPUnit_Framework_TestCase { public static $baseCardTypes = [ @@ -19,6 +24,8 @@ class FormTest extends \PHPUnit_Framework_TestCase 'MC' => 'MasterCard', 'DI' => 'Discover', 'JBC' => 'JBC', + 'CUP' => 'China Union Pay', + 'MI' => 'Maestro', 'OT' => 'Other' ]; @@ -31,11 +38,6 @@ class FormTest extends \PHPUnit_Framework_TestCase */ private $block; - /** - * @var \Magento\Payment\Model\Config|\PHPUnit_Framework_MockObject_MockObject - */ - private $paymentConfig; - /** * @var \Magento\Backend\Model\Session\Quote|\PHPUnit_Framework_MockObject_MockObject */ @@ -46,17 +48,24 @@ class FormTest extends \PHPUnit_Framework_TestCase */ private $gatewayConfig; + /** + * @var \Magento\BraintreeTwo\Model\Adminhtml\Source\CcType|\PHPUnit_Framework_MockObject_MockObject + */ + private $ccType; + + protected function setUp() { - $this->initPaymentConfigMock(); + $this->initCcTypeMock(); $this->initSessionQuoteMock(); $this->initGatewayConfigMock(); $managerHelper = new ObjectManager($this); $this->block = $managerHelper->getObject(Form::class, [ - 'paymentConfig' => $this->paymentConfig, + 'paymentConfig' => $managerHelper->getObject(Config::class), 'sessionQuote' => $this->sessionQuote, - 'gatewayConfig' => $this->gatewayConfig + 'gatewayConfig' => $this->gatewayConfig, + 'ccType' => $this->ccType ]); } @@ -101,16 +110,16 @@ public function countryCardTypesDataProvider() } /** - * Create mock for payment config + * Create mock for credit card type */ - private function initPaymentConfigMock() + private function initCcTypeMock() { - $this->paymentConfig = $this->getMockBuilder(Config::class) + $this->ccType = $this->getMockBuilder(CcType::class) ->disableOriginalConstructor() ->setMethods(['getCcTypes']) ->getMock(); - $this->paymentConfig->expects(static::once()) + $this->ccType->expects(static::once()) ->method('getCcTypes') ->willReturn(self::$baseCardTypes); } diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php index 411311e0433ad..01390f9d238d3 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Helper/CountryTest.php @@ -80,7 +80,7 @@ protected function getCollectionFactoryMock() ->method('loadData') ->willReturnSelf(); - $collectionFactory = $this->getMockBuilder('\Magento\Directory\Model\ResourceModel\Country\CollectionFactory') + $collectionFactory = $this->getMockBuilder(CollectionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php index ac80f37cd0b34..449c07fd80b2d 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Adminhtml/System/Config/CountryTest.php @@ -136,7 +136,6 @@ protected function initCountryCollectionMock(array $countries) { $this->countryCollectionMock->expects(static::once()) ->method('addFieldToFilter') - ->with('country_id', ['nin' => Country::$excludedCountries]) ->willReturnSelf(); $this->countryCollectionMock->expects(static::once()) ->method('loadData') diff --git a/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml b/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml index cb97762b5077e..eb0c275ee2edc 100644 --- a/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml +++ b/app/code/Magento/BraintreeTwo/etc/adminhtml/system.xml @@ -65,6 +65,7 @@ Magento\Config\Block\System\Config\Form\Fieldset + If you don't specify the merchant account to use to process a transaction, Braintree will process it using your default merchant account payment/braintreetwo/merchant_account_id diff --git a/app/code/Magento/BraintreeTwo/etc/config.xml b/app/code/Magento/BraintreeTwo/etc/config.xml index 7146b3b8e3739..a0556c40df792 100644 --- a/app/code/Magento/BraintreeTwo/etc/config.xml +++ b/app/code/Magento/BraintreeTwo/etc/config.xml @@ -17,9 +17,9 @@ 1 1 1 - AE,VI,MC,DI,JCB + AE,VI,MC,DI,JCB,CUP,DN,MI 1 - + 1 processing sandbox diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml index 14c65994565ad..d146fb477799a 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml @@ -38,11 +38,15 @@
- +
From 55fa2861ad1dcabee6f0464920eae012bb03d8ac Mon Sep 17 00:00:00 2001 From: Ievgen Sentiabov Date: Tue, 1 Dec 2015 15:37:30 +0200 Subject: [PATCH 28/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Refactored code related to code review - Added @todo annotations for refactoring after changes in payment and sales module --- .../Form/Field/CountryCreditCard.php | 3 -- app/code/Magento/BraintreeTwo/Block/Form.php | 7 +++-- app/code/Magento/BraintreeTwo/Block/Info.php | 30 ++++--------------- .../BraintreeTwo/Gateway/Config/Config.php | 8 ++--- .../Gateway/Http/Client/TransactionSale.php | 2 +- .../Gateway/Response/CardDetailsHandler.php | 7 +++-- .../Response/PaymentDetailsHandler.php | 7 +++-- .../Model/Adminhtml/Source/CcType.php | 14 ++++----- .../System/Config/CountryCreditCard.php | 17 +++++++++-- .../BraintreeTwo/Model/Ui/ConfigProvider.php | 6 ++-- .../Observer/DataAssignObserver.php | 3 ++ .../BraintreeTwo/Test/Unit/Block/FormTest.php | 8 ++--- .../Test/Unit/Gateway/Config/ConfigTest.php | 16 +++++----- .../Test/Unit/Model/Ui/ConfigProviderTest.php | 8 ++--- app/code/Magento/BraintreeTwo/i18n/en_US.csv | 8 +++++ .../view/base/web/js/validator.js | 4 +-- 16 files changed, 76 insertions(+), 72 deletions(-) create mode 100644 app/code/Magento/BraintreeTwo/i18n/en_US.csv diff --git a/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php index 795a01ca200de..332e0d90efaf1 100644 --- a/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php +++ b/app/code/Magento/BraintreeTwo/Block/Adminhtml/Form/Field/CountryCreditCard.php @@ -97,9 +97,6 @@ protected function _prepareArrayRow(DataObject $row) = 'selected="selected"'; $ccTypes = $row->getCcTypes(); - if (!is_array($ccTypes)) { - $ccTypes = [$ccTypes]; - } foreach ($ccTypes as $cardType) { $options['option_' . $this->getCcTypesRenderer()->calcOptionHash($cardType)] = 'selected="selected"'; diff --git a/app/code/Magento/BraintreeTwo/Block/Form.php b/app/code/Magento/BraintreeTwo/Block/Form.php index dc46508a1c6d8..278b7490b863d 100644 --- a/app/code/Magento/BraintreeTwo/Block/Form.php +++ b/app/code/Magento/BraintreeTwo/Block/Form.php @@ -39,6 +39,7 @@ class Form extends Cc * @param \Magento\Payment\Model\Config $paymentConfig * @param \Magento\Backend\Model\Session\Quote $sessionQuote * @param \Magento\BraintreeTwo\Gateway\Config\Config $gatewayConfig + * @param \Magento\BraintreeTwo\Model\Adminhtml\Source\CcType $ccType * @param array $data */ public function __construct( @@ -72,7 +73,7 @@ public function getCcAvailableTypes() */ public function useCvv() { - return $this->gatewayConfig->useCvv(); + return $this->gatewayConfig->isCvvEnabled(); } /** @@ -81,8 +82,8 @@ public function useCvv() */ private function getConfiguredCardTypes() { - $types = $this->ccType->getCcTypes(); - $configCardTypes = array_fill_keys($this->gatewayConfig->getCcAvailableCardTypes(), ''); + $types = $this->ccType->getCcTypeLabelMap(); + $configCardTypes = array_fill_keys($this->gatewayConfig->getAvailableCardTypes(), ''); return array_intersect_key($types, $configCardTypes); } diff --git a/app/code/Magento/BraintreeTwo/Block/Info.php b/app/code/Magento/BraintreeTwo/Block/Info.php index 933150cb26564..b18032b805a3d 100644 --- a/app/code/Magento/BraintreeTwo/Block/Info.php +++ b/app/code/Magento/BraintreeTwo/Block/Info.php @@ -5,12 +5,13 @@ */ namespace Magento\BraintreeTwo\Block; -use Magento\BraintreeTwo\Gateway\Response\CardDetailsHandler; -use Magento\BraintreeTwo\Gateway\Response\PaymentDetailsHandler; use Magento\Framework\Phrase; use Magento\Payment\Block\ConfigurableInfo; -use Magento\Sales\Api\Data\OrderPaymentInterface; +/** + * Class Info + * @package Magento\BraintreeTwo\Block + */ class Info extends ConfigurableInfo { /** @@ -18,30 +19,9 @@ class Info extends ConfigurableInfo * * @param string $field * @return Phrase - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function getLabel($field) { - switch ($field) { - case OrderPaymentInterface::CC_TYPE: - return __('Credit Card Type'); - case CardDetailsHandler::CARD_NUMBER: - return __('Credit Card Number'); - case PaymentDetailsHandler::AVS_POSTAL_RESPONSE_CODE: - return __('AVS Postal Code Response Code'); - case PaymentDetailsHandler::AVS_STREET_ADDRESS_RESPONSE_CODE: - return __('Avs Street Address Response Code'); - case PaymentDetailsHandler::CVV_RESPONSE_CODE: - return __('Cvv Response Code'); - case PaymentDetailsHandler::PROCESSOR_AUTHORIZATION_CODE: - return __('Processor Authorization Code'); - case PaymentDetailsHandler::PROCESSOR_RESPONSE_CODE: - return __('Processor Response Code'); - case PaymentDetailsHandler::PROCESSOR_RESPONSE_TEXT: - return __('Processor Response Text'); - default: - return parent::getLabel($field); - } + return __($field); } } diff --git a/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php b/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php index 5790972b48afa..651eef51162b4 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Config/Config.php @@ -90,7 +90,7 @@ public function initCredentials() /** * Generate a new client token if necessary - * + * @TODO method should be moved to adapter * @return string */ public function getClientToken() @@ -119,7 +119,7 @@ public function getCountrySpecificCardTypeConfig() * * @return array */ - public function getCcAvailableCardTypes() + public function getAvailableCardTypes() { $ccTypes = $this->getValue(self::KEY_CC_TYPES); @@ -131,7 +131,7 @@ public function getCcAvailableCardTypes() * * @return array */ - public function getCctypesMapper() + public function getCcTypesMapper() { $result = json_decode( $this->getValue(self::KEY_CC_TYPES_BRAINTREE_MAPPER), @@ -156,7 +156,7 @@ public function getCountryAvailableCardTypes($country) * Check if cvv field is enabled * @return boolean */ - public function useCvv() + public function isCvvEnabled() { return (bool) $this->getValue(self::KEY_USE_CVV); } diff --git a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php index 29fb6801e3bd1..4896e81d70600 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Http/Client/TransactionSale.php @@ -56,7 +56,7 @@ public function placeRequest(TransferInterface $transferObject) $response['object'] = $this->braintreeTransaction->sale($data); } catch (\Exception $e) { throw new ClientException(__( - $e->getMessage() ?: 'Transaction has been declined, please, try again later.' + $e->getMessage() ?: 'Sorry, but something went wrong' )); } finally { $log['response'] = (array) $response['object']; diff --git a/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php index f9c8133dccd92..975f17b0a1ada 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Response/CardDetailsHandler.php @@ -47,11 +47,14 @@ public function __construct(Config $config) */ public function handle(array $handlingSubject, array $response) { - $details = SubjectReader::readPayment($handlingSubject); + $paymentDO = SubjectReader::readPayment($handlingSubject); /** @var \Braintree_Transaction $transaction */ $transaction = $response['object']->transaction; + /** + * @TODO after changes in sales module should be refactored for new interfaces + */ /** @var \Magento\Sales\Model\Order\Payment $payment */ - $payment = $details->getPayment(); + $payment = $paymentDO->getPayment(); ContextHelper::assertOrderPayment($payment); $creditCard = $transaction->creditCard; diff --git a/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php b/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php index 8cf801dafc3b7..80d68bf263477 100644 --- a/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php +++ b/app/code/Magento/BraintreeTwo/Gateway/Response/PaymentDetailsHandler.php @@ -48,11 +48,14 @@ class PaymentDetailsHandler implements HandlerInterface */ public function handle(array $handlingSubject, array $response) { - $details = SubjectReader::readPayment($handlingSubject); + $paymentDO = SubjectReader::readPayment($handlingSubject); /** @var \Braintree_Transaction $transaction */ $transaction = $response['object']->transaction; + /** + * @TODO after changes in sales module should be refactored for new interfaces + */ /** @var \Magento\Sales\Model\Order\Payment $payment */ - $payment = $details->getPayment(); + $payment = $paymentDO->getPayment(); ContextHelper::assertOrderPayment($payment); $payment->setTransactionId($transaction->id); diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php index d5119ffa9e647..aadf05462aa1a 100644 --- a/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/Source/CcType.php @@ -5,19 +5,17 @@ */ namespace Magento\BraintreeTwo\Model\Adminhtml\Source; -use Magento\Payment\Model\Source\Cctype as PaymentCcType; - /** * Class CcType * @codeCoverageIgnore */ -class CcType extends PaymentCctype +class CcType extends \Magento\Payment\Model\Source\Cctype { /** * List of specific credit card types * @var array */ - private $mapper = [ + private $specificCardTypesList = [ 'CUP' => 'China Union Pay' ]; @@ -32,13 +30,13 @@ public function getAllowedTypes() } /** - * Getting credit cards types + * Returns credit cards types * * @return array */ - public function getCcTypes() + public function getCcTypeLabelMap() { - return array_merge($this->mapper, $this->_paymentConfig->getCcTypes()); + return array_merge($this->specificCardTypesList, $this->_paymentConfig->getCcTypes()); } /** @@ -49,7 +47,7 @@ public function toOptionArray() $allowed = $this->getAllowedTypes(); $options = []; - foreach ($this->getCcTypes() as $code => $name) { + foreach ($this->getCcTypeLabelMap() as $code => $name) { if (in_array($code, $allowed)) { $options[] = ['value' => $code, 'label' => $name]; } diff --git a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php index 80af387a5cadf..b8855a87a5c2b 100644 --- a/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php +++ b/app/code/Magento/BraintreeTwo/Model/Adminhtml/System/Config/CountryCreditCard.php @@ -64,9 +64,7 @@ public function beforeSave() } $country = $data['country_id']; if (array_key_exists($country, $result)) { - $result[$country] = array_merge($result[$country], $data['cc_types']); - // filter and reindex array - $result[$country] = array_values(array_unique($result[$country])); + $result[$country] = $this->appendUniqueCountries($result[$country], $data['cc_types']); } else { $result[$country] = $data['cc_types']; } @@ -105,4 +103,17 @@ protected function encodeArrayFieldValue(array $value) } return $result; } + + /** + * Append unique countries to list of exists and reindex keys + * + * @param array $countriesList + * @param array $inputCountriesList + * @return array + */ + private function appendUniqueCountries(array $countriesList, array $inputCountriesList) + { + $result = array_merge($countriesList, $inputCountriesList); + return array_values(array_unique($result)); + } } diff --git a/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php b/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php index 300ecd944abd4..cd2fe2055956d 100644 --- a/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php +++ b/app/code/Magento/BraintreeTwo/Model/Ui/ConfigProvider.php @@ -41,11 +41,11 @@ public function getConfig() 'payment' => [ self::CODE => [ 'clientToken' => $this->config->getClientToken(), - 'cctypesMapper' => $this->config->getCctypesMapper(), + 'ccTypesMapper' => $this->config->getCctypesMapper(), 'sdkUrl' => $this->config->getValue(Config::KEY_SDK_URL), 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig(), - 'availableCardTypes' => $this->config->getCcAvailableCardTypes(), - 'useCvv' => $this->config->useCvv() + 'availableCardTypes' => $this->config->getAvailableCardTypes(), + 'useCvv' => $this->config->isCvvEnabled() ], ] ]; diff --git a/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php index 8b779600fdeab..8da4f09767cbd 100644 --- a/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php +++ b/app/code/Magento/BraintreeTwo/Observer/DataAssignObserver.php @@ -23,6 +23,9 @@ public function execute(Observer $observer) { $method = $this->readMethodArgument($observer); $data = $this->readDataArgument($observer); + /** + * @TODO should be refactored after interface changes in payment and quote + */ $paymentInfo = $method->getInfoInstance(); if ($data->getDataByKey(self::PAYMENT_METHOD_NONCE) !== null) { $paymentInfo->setAdditionalInformation( diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php index 2f41f3556cd9b..8b3732302c1ff 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Block/FormTest.php @@ -83,7 +83,7 @@ public function testGetCcAvailableTypes($countryId, array $availableTypes, array ->willReturn($countryId); $this->gatewayConfig->expects(static::once()) - ->method('getCcAvailableCardTypes') + ->method('getAvailableCardTypes') ->willReturn(self::$configCardTypes); $this->gatewayConfig->expects(static::once()) @@ -116,11 +116,11 @@ private function initCcTypeMock() { $this->ccType = $this->getMockBuilder(CcType::class) ->disableOriginalConstructor() - ->setMethods(['getCcTypes']) + ->setMethods(['getCcTypeLabelMap']) ->getMock(); $this->ccType->expects(static::once()) - ->method('getCcTypes') + ->method('getCcTypeLabelMap') ->willReturn(self::$baseCardTypes); } @@ -149,7 +149,7 @@ private function initGatewayConfigMock() { $this->gatewayConfig = $this->getMockBuilder(GatewayConfig::class) ->disableOriginalConstructor() - ->setMethods(['getCountryAvailableCardTypes', 'getCcAvailableCardTypes']) + ->setMethods(['getCountryAvailableCardTypes', 'getAvailableCardTypes']) ->getMock(); } } diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php index 988933fdad181..c275b3a8aa2de 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Gateway/Config/ConfigTest.php @@ -174,9 +174,9 @@ public function getCountrySpecificCardTypeConfigDataProvider() /** * @param string $value * @param array $expected - * @dataProvider getCcAvailableCardTypesDataProvider + * @dataProvider getAvailableCardTypesDataProvider */ - public function testGetCcAvailableCardTypes($value, $expected) + public function testGetAvailableCardTypes($value, $expected) { $this->scopeConfigMock->expects(static::once()) ->method('getValue') @@ -185,14 +185,14 @@ public function testGetCcAvailableCardTypes($value, $expected) static::assertEquals( $expected, - $this->model->getCcAvailableCardTypes() + $this->model->getAvailableCardTypes() ); } /** * @return array */ - public function getCcAvailableCardTypesDataProvider() + public function getAvailableCardTypesDataProvider() { return [ [ @@ -209,9 +209,9 @@ public function getCcAvailableCardTypesDataProvider() /** * @param string $value * @param array $expected - * @dataProvider getCctypesMapperDataProvider + * @dataProvider getCcTypesMapperDataProvider */ - public function testGetCctypesMapper($value, $expected) + public function testGetCcTypesMapper($value, $expected) { $this->scopeConfigMock->expects(static::once()) ->method('getValue') @@ -227,7 +227,7 @@ public function testGetCctypesMapper($value, $expected) /** * @return array */ - public function getCctypesMapperDataProvider() + public function getCcTypesMapperDataProvider() { return [ [ @@ -269,7 +269,7 @@ public function testUseCvv() ->with($this->getPath(Config::KEY_USE_CVV), ScopeInterface::SCOPE_STORE, null) ->willReturn(1); - static::assertEquals(true, $this->model->useCvv()); + static::assertEquals(true, $this->model->isCvvEnabled()); } /** diff --git a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php index e9ee654cd954d..3fbbc73939233 100644 --- a/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php +++ b/app/code/Magento/BraintreeTwo/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -60,19 +60,19 @@ public function getConfigDataProvider() [ 'config' => [ 'getClientToken' => 'token', - 'getCctypesMapper' => ['visa' => 'VI', 'american-express'=> 'AE'], + 'getCcTypesMapper' => ['visa' => 'VI', 'american-express'=> 'AE'], 'getCountrySpecificCardTypeConfig' => [ 'GB' => ['VI', 'AE'], 'US' => ['DI', 'JCB'] ], - 'getCcAvailableCardTypes' => ['AE', 'VI', 'MC', 'DI', 'JCB'], - 'useCvv' => true + 'getAvailableCardTypes' => ['AE', 'VI', 'MC', 'DI', 'JCB'], + 'isCvvEnabled' => true ], 'expected' => [ 'payment' => [ ConfigProvider::CODE => [ 'clientToken' => 'token', - 'cctypesMapper' => ['visa' => 'VI', 'american-express' => 'AE'], + 'ccTypesMapper' => ['visa' => 'VI', 'american-express' => 'AE'], 'sdkUrl' => self::SDK_URL, 'countrySpecificCardTypes' =>[ 'GB' => ['VI', 'AE'], diff --git a/app/code/Magento/BraintreeTwo/i18n/en_US.csv b/app/code/Magento/BraintreeTwo/i18n/en_US.csv new file mode 100644 index 0000000000000..0a0c29beffa1c --- /dev/null +++ b/app/code/Magento/BraintreeTwo/i18n/en_US.csv @@ -0,0 +1,8 @@ +"cc_type","Credit Card Type" +"cc_number","Credit Card Number" +"avsPostalCodeResponseCode","AVS Postal Code Response Code" +"avsStreetAddressResponseCode","Avs Street Address Response Code" +"cvvResponseCode","Cvv Response Code" +"processorAuthorizationCode","Processor Authorization Code" +"processorResponseCode","Processor Response Code" +"processorResponseText","Processor Response Text" \ No newline at end of file diff --git a/app/code/Magento/BraintreeTwo/view/base/web/js/validator.js b/app/code/Magento/BraintreeTwo/view/base/web/js/validator.js index fcc295797cc7b..084c61795d75a 100644 --- a/app/code/Magento/BraintreeTwo/view/base/web/js/validator.js +++ b/app/code/Magento/BraintreeTwo/view/base/web/js/validator.js @@ -30,10 +30,10 @@ define([ /** * Get list of card types - * @returns {exports.defaults.cctypesMapper|{}|*} + * @returns {exports.defaults.ccTypesMapper|{}|*} */ getCcTypesMapper: function () { - return this.config.cctypesMapper; + return this.config.ccTypesMapper; }, /** From cbe9b55e08ca6d1ace679a503e969c80b17a5646 Mon Sep 17 00:00:00 2001 From: Ievgen Sentiabov Date: Tue, 1 Dec 2015 15:57:51 +0200 Subject: [PATCH 29/79] MAGETWO-45180: [Refactoring] Accept Credit Card Payments using Braintree for StoreFront and Backend - Fixed failed static tests --- app/code/Magento/BraintreeTwo/Helper/Country.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/BraintreeTwo/Helper/Country.php b/app/code/Magento/BraintreeTwo/Helper/Country.php index 054a29d9b7c24..8065a3ca4c61d 100644 --- a/app/code/Magento/BraintreeTwo/Helper/Country.php +++ b/app/code/Magento/BraintreeTwo/Helper/Country.php @@ -5,7 +5,6 @@ */ namespace Magento\BraintreeTwo\Helper; -use Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country as CountryConfig; use Magento\Directory\Model\ResourceModel\Country\CollectionFactory; /** @@ -30,10 +29,13 @@ class Country private $countries; /** - * @param CollectionFactory $factory + * @param \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $factory + * @param \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country $countryConfig */ - public function __construct(CollectionFactory $factory, CountryConfig $countryConfig) - { + public function __construct( + \Magento\Directory\Model\ResourceModel\Country\CollectionFactory $factory, + \Magento\BraintreeTwo\Model\Adminhtml\System\Config\Country $countryConfig + ) { $this->collectionFactory = $factory; $this->countryConfig = $countryConfig; } From 5e6e1b53367cf3ffb95c3cc83d14723b832971de Mon Sep 17 00:00:00 2001 From: Serhiy Shkolyarenko Date: Tue, 1 Dec 2015 15:59:30 +0200 Subject: [PATCH 30/79] MAGETWO-44852: [Folks] TAF activity S77 updates CR changes --- .../Checkout/Test/Block/Cart/Sidebar.php | 24 +++++++++++++++++-- .../Test/Constraint/AssertMinicartEmpty.php | 15 ++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php index 6d6bd9e9927d4..388fc69bff50a 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php @@ -30,9 +30,18 @@ class Sidebar extends Block protected $cartLink = 'a.showcart'; /** + * Minicart items quantity + * + * @var string + */ + protected $productCounter = './/*[@class="counter-number"]'; + + /** + * Empty minicart message + * * @var string */ - protected $productCounter = '//*[@data-block="minicart"]//*[@class="counter-number"]'; + protected $emptyCartMessage = './/*[@id="minicart-content-wrapper"]//*[@class="subtitle empty"]'; /** * Mini cart content selector. @@ -107,10 +116,21 @@ function () use ($browser, $selector) { } /** - * Get cart items quantity + * Get empty minicart message * * @return string */ + public function getEmptyMessage() + { + $this->_rootElement->find($this->cartLink)->click(); + return $this->_rootElement->find($this->emptyCartMessage, Locator::SELECTOR_XPATH)->getText(); + } + + /** + * Is minicart items quantity block visible + * + * @return bool + */ public function isItemsQtyVisible() { return $this->_rootElement->find($this->productCounter, Locator::SELECTOR_XPATH)->isVisible(); diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php index 7d5a44a7720fb..80de7ea365fa8 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertMinicartEmpty.php @@ -15,13 +15,24 @@ class AssertMinicartEmpty extends AbstractConstraint { /** - * Assert that customer cart is empty + * Empty cart message + */ + const TEXT_EMPTY_MINICART = 'You have no items in your shopping cart.'; + + /** + * Assert that customer minicart is empty * * @param CmsIndex $cmsIndex */ public function processAssert( CmsIndex $cmsIndex ) { + \PHPUnit_Framework_Assert::assertEquals( + self::TEXT_EMPTY_MINICART, + $cmsIndex->getCartSidebarBlock()->getEmptyMessage(), + 'Empty minicart message not found' + ); + \PHPUnit_Framework_Assert::assertFalse( $cmsIndex->getCartSidebarBlock()->isItemsQtyVisible(), 'Minicart is not empty' @@ -35,6 +46,6 @@ public function processAssert( */ public function toString() { - return 'Minicart emptiness check'; + return 'Minicart is empty'; } } From e54987b167c6268b63ff65f9743432bfdb7e3839 Mon Sep 17 00:00:00 2001 From: "Yushkin, Dmytro" Date: Tue, 1 Dec 2015 18:56:52 +0200 Subject: [PATCH 31/79] MAGETWO-45682: Create automated functional tests for Braintree - Fields strict set to "0" - Fix expiration date fields --- .../app/Magento/Braintree/Test/Block/Form/Cc.php | 3 ++- .../app/Magento/Braintree/Test/Block/Form/Cc.xml | 11 +++++++---- .../Braintree/Test/Fixture/CreditCardBraintree.xml | 3 ++- .../Magento/Braintree/Test/Repository/ConfigData.xml | 12 ++++++------ .../Magento/Braintree/Test/Repository/CreditCard.xml | 3 ++- .../tests/app/Magento/Payment/Test/Block/Form/Cc.xml | 2 +- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php index cd933f3993cc0..a9e6ca39baa76 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.php @@ -28,7 +28,8 @@ class Cc extends CreditCard */ protected $braintreeForm = [ "credit_card_number" => "#braintree-hosted-field-number", - "expiration" => "#braintree-hosted-field-expirationDate", + "credit_card_exp_month" => "#braintree-hosted-field-expirationMonth", + "credit_card_exp_year" => "#braintree-hosted-field-expirationYear", "cvv" => "#braintree-hosted-field-cvv", ]; diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml index 394609e2ca7c1..014b18608c7f2 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Block/Form/Cc.xml @@ -5,14 +5,17 @@ * See COPYING.txt for license details. */ --> - + #credit-card-number - - #expiration - + + #expiration-month + + + #expiration-year + #cvv diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml index 42bc599eea401..3b1732342ee17 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Fixture/CreditCardBraintree.xml @@ -13,7 +13,8 @@ repository_class="Magento\Braintree\Test\Repository\CreditCard" class="Magento\Braintree\Test\Fixture\CreditCardBraintree"> - + + diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml index 86c039151d322..60aaa6e532414 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/ConfigData.xml @@ -8,12 +8,6 @@ - - payment - 1 - Yes - PAYMENT_BRAINTREETWO_MERCHANT_ACCOUNT_ID - payment 1 @@ -32,6 +26,12 @@ PAYMENT_BRAINTREETWO_PRIVATE_KEY + + payment + 1 + Yes + PAYMENT_BRAINTREETWO_MERCHANT_ACCOUNT_ID + payment 1 diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml index bf759b37a45b5..855fc144880db 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Repository/CreditCard.xml @@ -9,7 +9,8 @@ 4111111111111111 - 01/2020 + 01 + 2020 123 diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/Block/Form/Cc.xml b/dev/tests/functional/tests/app/Magento/Payment/Test/Block/Form/Cc.xml index 338d574835d67..652e00245a450 100644 --- a/dev/tests/functional/tests/app/Magento/Payment/Test/Block/Form/Cc.xml +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/Block/Form/Cc.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> - + payment From afc1141cd6ca0fa58b956845a6c04f1088f77ff7 Mon Sep 17 00:00:00 2001 From: Alex Akimov Date: Wed, 2 Dec 2015 13:15:06 +0200 Subject: [PATCH 32/79] MAGETWO-46000: Terms and Conditions Checkbox Text is Hard-Coded as a Title in the Terms and Conditions Template --- .../view/frontend/web/template/checkout/checkout-agreements.html | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html index 3f0429aa0fca3..2305aa15db4a9 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html +++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html @@ -37,7 +37,6 @@
From ef139627670238a1bef14b91b60ab2e3be25dcd4 Mon Sep 17 00:00:00 2001 From: Serhiy Shkolyarenko Date: Wed, 2 Dec 2015 13:28:53 +0200 Subject: [PATCH 33/79] MAGETWO-46206: Fix random fails of Magento\Checkout\Test\TestCase\OnePageCheckoutTest functional test fixed javascript error --- .../GiftMessage/view/frontend/web/js/view/gift-message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/GiftMessage/view/frontend/web/js/view/gift-message.js b/app/code/Magento/GiftMessage/view/frontend/web/js/view/gift-message.js index fc4d6931be2e0..50bd8f91ed516 100644 --- a/app/code/Magento/GiftMessage/view/frontend/web/js/view/gift-message.js +++ b/app/code/Magento/GiftMessage/view/frontend/web/js/view/gift-message.js @@ -70,7 +70,7 @@ define([ hasActiveOptions: function() { var regionData = this.getRegion('additionalOptions'); var options = regionData(); - for (var i in options) { + for (var i = 0; i < options.length; i++) { if (options[i].isActive()) { return true; } From 862bb3fc1661386951cdfa68b92c2676617c5650 Mon Sep 17 00:00:00 2001 From: "Gurzhyi, Andrii" Date: Wed, 2 Dec 2015 14:06:21 +0200 Subject: [PATCH 34/79] MAGETWO-46207: Integration Vault of payment solution - Integration --- .../view/frontend/web/js/view/payment/list.js | 75 +-- .../Gateway/CommandExecuteInterface.php | 21 + .../Magento/Payment/Model/Method/Adapter.php | 164 +++---- .../Vault/Api/Data/PaymentTokenInterface.php | 224 +++++++++ .../PaymentTokenSearchResultsInterface.php | 29 ++ .../Api/PaymentTokenManagementInterface.php | 49 ++ .../Api/PaymentTokenRepositoryInterface.php | 46 ++ .../Vault/Gateway/Config/ActiveHandler.php | 41 ++ app/code/Magento/Vault/LICENSE.txt | 48 ++ app/code/Magento/Vault/LICENSE_AFL.txt | 48 ++ .../Model/Adminhtml/Source/VaultPayment.php | 49 ++ app/code/Magento/Vault/Model/Method/Vault.php | 462 ++++++++++++++++++ app/code/Magento/Vault/Model/PaymentToken.php | 215 ++++++++ .../Vault/Model/PaymentTokenManagement.php | 159 ++++++ .../Model/PaymentTokenNullRepository.php | 79 +++ .../Vault/Model/PaymentTokenRepository.php | 162 ++++++ .../Model/PaymentTokenRepositoryProxy.php | 131 +++++ .../Model/ResourceModel/PaymentToken.php | 81 +++ .../ResourceModel/PaymentToken/Collection.php | 22 + .../Magento/Vault/Model/Ui/ConfigProvider.php | 98 ++++ .../Vault/Model/VaultPaymentInterface.php | 24 + .../Observer/AfterPaymentSaveObserver.php | 131 +++++ .../Plugin/PaymentVaultAttributesLoad.php | 67 +++ app/code/Magento/Vault/README.md | 1 + .../Magento/Vault/Setup/InstallSchema.php | 169 +++++++ .../Unit/Gateway/Config/ActiveHandlerTest.php | 90 ++++ .../Unit/Model/PaymentTokenManagementTest.php | 259 ++++++++++ .../Model/PaymentTokenNullRepositoryTest.php | 127 +++++ .../Model/PaymentTokenRepositoryProxyTest.php | 239 +++++++++ .../Unit/Model/PaymentTokenRepositoryTest.php | 211 ++++++++ .../Observer/AfterPaymentSaveObserverTest.php | 209 ++++++++ .../Vault/Test/Unit/Ui/ConfigProviderTest.php | 179 +++++++ app/code/Magento/Vault/composer.json | 26 + app/code/Magento/Vault/etc/config.xml | 19 + app/code/Magento/Vault/etc/di.xml | 69 +++ app/code/Magento/Vault/etc/events.xml | 12 + .../Vault/etc/extension_attributes.xml | 12 + app/code/Magento/Vault/etc/frontend/di.xml | 16 + app/code/Magento/Vault/etc/module.xml | 16 + app/code/Magento/Vault/registration.php | 11 + .../frontend/layout/checkout_index_index.xml | 45 ++ .../js/view/payment/method-renderer/vault.js | 87 ++++ .../frontend/web/js/view/payment/vault.js | 35 ++ .../frontend/web/template/payment/form.html | 40 ++ composer.json | 1 + composer.lock | 285 ++++++----- .../Magento/Vault/Model/PaymentTokenTest.php | 27 + .../order_paid_with_payflowpro_vault.php | 70 +++ 48 files changed, 4427 insertions(+), 253 deletions(-) create mode 100644 app/code/Magento/Payment/Gateway/CommandExecuteInterface.php create mode 100644 app/code/Magento/Vault/Api/Data/PaymentTokenInterface.php create mode 100644 app/code/Magento/Vault/Api/Data/PaymentTokenSearchResultsInterface.php create mode 100644 app/code/Magento/Vault/Api/PaymentTokenManagementInterface.php create mode 100644 app/code/Magento/Vault/Api/PaymentTokenRepositoryInterface.php create mode 100644 app/code/Magento/Vault/Gateway/Config/ActiveHandler.php create mode 100644 app/code/Magento/Vault/LICENSE.txt create mode 100644 app/code/Magento/Vault/LICENSE_AFL.txt create mode 100644 app/code/Magento/Vault/Model/Adminhtml/Source/VaultPayment.php create mode 100644 app/code/Magento/Vault/Model/Method/Vault.php create mode 100644 app/code/Magento/Vault/Model/PaymentToken.php create mode 100644 app/code/Magento/Vault/Model/PaymentTokenManagement.php create mode 100644 app/code/Magento/Vault/Model/PaymentTokenNullRepository.php create mode 100644 app/code/Magento/Vault/Model/PaymentTokenRepository.php create mode 100644 app/code/Magento/Vault/Model/PaymentTokenRepositoryProxy.php create mode 100644 app/code/Magento/Vault/Model/ResourceModel/PaymentToken.php create mode 100644 app/code/Magento/Vault/Model/ResourceModel/PaymentToken/Collection.php create mode 100644 app/code/Magento/Vault/Model/Ui/ConfigProvider.php create mode 100644 app/code/Magento/Vault/Model/VaultPaymentInterface.php create mode 100644 app/code/Magento/Vault/Observer/AfterPaymentSaveObserver.php create mode 100644 app/code/Magento/Vault/Plugin/PaymentVaultAttributesLoad.php create mode 100644 app/code/Magento/Vault/README.md create mode 100644 app/code/Magento/Vault/Setup/InstallSchema.php create mode 100644 app/code/Magento/Vault/Test/Unit/Gateway/Config/ActiveHandlerTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Model/PaymentTokenManagementTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Model/PaymentTokenNullRepositoryTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Model/PaymentTokenRepositoryProxyTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Model/PaymentTokenRepositoryTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Observer/AfterPaymentSaveObserverTest.php create mode 100644 app/code/Magento/Vault/Test/Unit/Ui/ConfigProviderTest.php create mode 100644 app/code/Magento/Vault/composer.json create mode 100644 app/code/Magento/Vault/etc/config.xml create mode 100644 app/code/Magento/Vault/etc/di.xml create mode 100644 app/code/Magento/Vault/etc/events.xml create mode 100644 app/code/Magento/Vault/etc/extension_attributes.xml create mode 100644 app/code/Magento/Vault/etc/frontend/di.xml create mode 100644 app/code/Magento/Vault/etc/module.xml create mode 100644 app/code/Magento/Vault/registration.php create mode 100644 app/code/Magento/Vault/view/frontend/layout/checkout_index_index.xml create mode 100644 app/code/Magento/Vault/view/frontend/web/js/view/payment/method-renderer/vault.js create mode 100644 app/code/Magento/Vault/view/frontend/web/js/view/payment/vault.js create mode 100644 app/code/Magento/Vault/view/frontend/web/template/payment/form.html create mode 100644 dev/tests/integration/testsuite/Magento/Vault/Model/PaymentTokenTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Vault/_files/order_paid_with_payflowpro_vault.php diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js index a65ec36aea72e..45e1bf4e01f00 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js @@ -54,6 +54,7 @@ define([ */ initChildren: function () { var self = this; + _.each(paymentMethods(), function (paymentMethodData) { self.createRenderer(paymentMethodData); }); @@ -62,50 +63,55 @@ define([ }, /** - * Create renderer. - * - * @param {Object} paymentMethodData + * @returns */ - createRenderer: function (paymentMethodData) { - var renderer = this.getRendererByType(paymentMethodData.method), - rendererTemplate, + createComponent: function (payment) { + var rendererTemplate, rendererComponent, templateData; - if (renderer) { - templateData = { - parentName: this.name, - name: paymentMethodData.method - }; - rendererTemplate = { - parent: '${ $.$data.parentName }', - name: '${ $.$data.name }', - displayArea: 'payment-method-items', - component: renderer.component - }; - rendererComponent = utils.template(rendererTemplate, templateData); - utils.extend(rendererComponent, { - item: paymentMethodData - }); - layout([rendererComponent]); - } + templateData = { + parentName: this.name, + name: payment.name + }; + rendererTemplate = { + parent: '${ $.$data.parentName }', + name: '${ $.$data.name }', + displayArea: 'payment-method-items', + component: payment.component + }; + rendererComponent = utils.template(rendererTemplate, templateData); + utils.extend(rendererComponent, { + item: payment.item, + config: payment.config + }); + + return rendererComponent; }, /** - * Get renderer for payment method type. + * Create renderer. * - * @param {String} paymentMethodCode - * @returns {Object} + * @param {Object} paymentMethodData */ - getRendererByType: function (paymentMethodCode) { - var compatibleRenderer; + createRenderer: function (paymentMethodData) { _.find(rendererList(), function (renderer) { - if (renderer.type === paymentMethodCode) { - compatibleRenderer = renderer; + if (renderer.type.indexOf(paymentMethodData.method) === 0) { + layout( + [ + this.createComponent( + { + config: renderer.config, + component: renderer.component, + name: renderer.type, + method: paymentMethodData.method, + item: paymentMethodData + } + ) + ] + ); } - }); - - return compatibleRenderer; + }.bind(this)); }, /** @@ -115,8 +121,9 @@ define([ */ removeRenderer: function (paymentMethodCode) { var items = this.getRegion('payment-method-items'); + _.find(items(), function (value) { - if (value.item.method === paymentMethodCode) { + if (value.item.method.indexOf(paymentMethodCode) === 0) { value.disposeSubscriptions(); this.removeChild(value); } diff --git a/app/code/Magento/Payment/Gateway/CommandExecuteInterface.php b/app/code/Magento/Payment/Gateway/CommandExecuteInterface.php new file mode 100644 index 0000000000000..ce541dbf72ba8 --- /dev/null +++ b/app/code/Magento/Payment/Gateway/CommandExecuteInterface.php @@ -0,0 +1,21 @@ +executeCommand( 'fetch_transaction_information', - $payment, - ['transactionId' => $transactionId] + ['payment' => $payment, 'transactionId' => $transactionId] ); } /** - * {inheritdoc} + * @inheritdoc */ public function order(InfoInterface $payment, $amount) { $this->executeCommand( 'order', - $payment, - ['amount' => $amount] + ['payment' => $payment, 'amount' => $amount] ); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function authorize(InfoInterface $payment, $amount) { $this->executeCommand( 'authorize', - $payment, - ['amount' => $amount] + ['payment' => $payment, 'amount' => $amount] ); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function capture(InfoInterface $payment, $amount) { $this->executeCommand( 'capture', - $payment, - ['amount' => $amount] + ['payment' => $payment, 'amount' => $amount] ); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function refund(InfoInterface $payment, $amount) { $this->executeCommand( 'refund', - $payment, - ['amount' => $amount] + ['payment' => $payment, 'amount' => $amount] ); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function cancel(InfoInterface $payment) { - $this->executeCommand( - 'cancel', - $payment - ); + $this->executeCommand('cancel', ['payment' => $payment]); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function void(InfoInterface $payment) { - $this->executeCommand( - 'void', - $payment - ); + $this->executeCommand('void', ['payment' => $payment]); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function acceptPayment(InfoInterface $payment) { - $this->executeCommand( - 'accept_payment', - $payment - ); + $this->executeCommand('accept_payment', ['payment' => $payment]); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function denyPayment(InfoInterface $payment) { - $this->executeCommand( - 'deny_payment', - $payment - ); + $this->executeCommand('deny_payment', ['payment' => $payment]); return $this; } /** - * Performs command - * - * @param string $commandCode - * @param InfoInterface $payment - * @param array $arguments - * @return void - * @throws NotFoundException - * @throws \Exception - * @throws \DomainException + * @inheritdoc */ - private function executeCommand($commandCode, InfoInterface $payment, array $arguments = []) + public function executeCommand($commandCode, array $arguments = []) { if ($this->canPerformCommand($commandCode)) { if ($this->commandPool === null) { @@ -524,17 +501,25 @@ private function executeCommand($commandCode, InfoInterface $payment, array $arg } try { + $command = $this->commandPool->get($commandCode); - $arguments['payment'] = $this->paymentDataObjectFactory->create($payment); - $command->execute($arguments); + + if (isset($arguments['payment'])) { + $arguments['payment'] = $this->paymentDataObjectFactory->create($arguments['payment']); + } + + return $command->execute($arguments); + } catch (NotFoundException $e) { throw $e; } } + + return null; } /** - * {inheritdoc} + * @inheritdoc */ public function getCode() { @@ -542,7 +527,7 @@ public function getCode() } /** - * {inheritdoc} + * @inheritdoc */ public function getTitle() { @@ -550,7 +535,7 @@ public function getTitle() } /** - * {inheritdoc} + * @inheritdoc */ public function setStore($storeId) { @@ -558,7 +543,7 @@ public function setStore($storeId) } /** - * {inheritdoc} + * @inheritdoc */ public function getStore() { @@ -566,7 +551,7 @@ public function getStore() } /** - * {inheritdoc} + * @inheritdoc */ public function getFormBlockType() { @@ -574,7 +559,7 @@ public function getFormBlockType() } /** - * {inheritdoc} + * @inheritdoc */ public function getInfoBlockType() { @@ -582,7 +567,7 @@ public function getInfoBlockType() } /** - * {inheritdoc} + * @inheritdoc */ public function getInfoInstance() { @@ -590,7 +575,7 @@ public function getInfoInstance() } /** - * {inheritdoc} + * @inheritdoc */ public function setInfoInstance(InfoInterface $info) { @@ -598,7 +583,7 @@ public function setInfoInstance(InfoInterface $info) } /** - * {inheritdoc} + * @inheritdoc * @param DataObject $data * @return $this */ @@ -617,21 +602,24 @@ public function assignData(\Magento\Framework\DataObject $data) } /** - * {inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function initialize($paymentAction, $stateObject) { $this->executeCommand( 'initialize', - $this->getInfoInstance(), - ['paymentAction' => $paymentAction, 'stateObject' => $stateObject] + [ + 'payment' => $this->getInfoInstance(), + 'paymentAction' => $paymentAction, + 'stateObject' => $stateObject + ] ); return $this; } /** - * {inheritdoc} + * @inheritdoc */ public function getConfigPaymentAction() { diff --git a/app/code/Magento/Vault/Api/Data/PaymentTokenInterface.php b/app/code/Magento/Vault/Api/Data/PaymentTokenInterface.php new file mode 100644 index 0000000000000..e42aee5094645 --- /dev/null +++ b/app/code/Magento/Vault/Api/Data/PaymentTokenInterface.php @@ -0,0 +1,224 @@ +config = $config; + } + + /** + * @inheritdoc + */ + public function handle(array $subject, $storeId = null) + { + $vaultPaymentCode = $this->config->getValue(VaultPayment::VALUE_CODE, $storeId); + + return (int) ((int)$this->config->getValue('active', $storeId) === 1 + && $vaultPaymentCode + && $vaultPaymentCode !== VaultPayment::EMPTY_VALUE); + } +} diff --git a/app/code/Magento/Vault/LICENSE.txt b/app/code/Magento/Vault/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Vault/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Vault/LICENSE_AFL.txt b/app/code/Magento/Vault/LICENSE_AFL.txt new file mode 100644 index 0000000000000..87943b95d43a5 --- /dev/null +++ b/app/code/Magento/Vault/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Vault/Model/Adminhtml/Source/VaultPayment.php b/app/code/Magento/Vault/Model/Adminhtml/Source/VaultPayment.php new file mode 100644 index 0000000000000..0c3cc362ad433 --- /dev/null +++ b/app/code/Magento/Vault/Model/Adminhtml/Source/VaultPayment.php @@ -0,0 +1,49 @@ +options = array_merge( + $options, + [ + 'value' => self::EMPTY_VALUE, + 'label' => __('Select a payment solution') + ] + ); + } + + /** + * Return array of options as value-label pairs + * + * @return array Format: array(array('value' => '', 'label' => '