From afc1e35cef2c6487af45e093f39bc06a5b8c308c Mon Sep 17 00:00:00 2001 From: Dennis Garding Date: Thu, 21 Sep 2023 13:20:36 +0200 Subject: [PATCH] fix(PT-13134): Prevent call create order with an empty cart --- .../AbstractPaypalPaymentController.php | 19 ++++ .../Exceptions/EmptyCartException.php | 19 ++++ Controllers/Frontend/PaypalUnifiedApm.php | 9 ++ Controllers/Frontend/PaypalUnifiedV2.php | 6 + .../PaypalUnifiedV2PayUponInvoice.php | 9 ++ ...PaypalUnifiedV2AdvancedCreditDebitCard.php | 6 + .../PaypalUnifiedV2SmartPaymentButtons.php | 6 + ...xceptionOnCreateOrderWithEmptyCartTest.php | 106 ++++++++++++++++++ .../Frontend/PaypalUnifiedV2Test.php | 19 +++- .../PaypalUnifiedV2ExpressCheckoutTest.php | 2 +- 10 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 Controllers/Frontend/Exceptions/EmptyCartException.php create mode 100644 Tests/Functional/Controller/Frontend/AbstractPayPalPaymentControllerThrowsExceptionOnCreateOrderWithEmptyCartTest.php diff --git a/Controllers/Frontend/AbstractPaypalPaymentController.php b/Controllers/Frontend/AbstractPaypalPaymentController.php index f145f03b..96a154ba 100644 --- a/Controllers/Frontend/AbstractPaypalPaymentController.php +++ b/Controllers/Frontend/AbstractPaypalPaymentController.php @@ -40,6 +40,7 @@ use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\CaptureDeclinedException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\CaptureFailedException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\CapturePendingException; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InstrumentDeclinedException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; @@ -226,6 +227,10 @@ protected function createPayPalOrder(PayPalOrderParameter $orderParameter) try { $this->logger->debug(sprintf('%s BEFORE CREATE PAYPAL ORDER', __METHOD__)); + if (empty($orderParameter->getCart()['content'])) { + throw new EmptyCartException(); + } + $payPalOrderData = $this->orderFactory->createOrder($orderParameter); $payPalOrder = $this->orderResource->create($payPalOrderData, $orderParameter->getPaymentType()); @@ -259,6 +264,8 @@ protected function createPayPalOrder(PayPalOrderParameter $orderParameter) return null; } catch (PuiValidationException $puiValidationException) { throw $puiValidationException; + } catch (EmptyCartException $emptyCartException) { + throw $emptyCartException; } catch (Exception $exception) { $redirectDataBuilder = $this->redirectDataBuilderFactory->createRedirectDataBuilder() ->setCode(ErrorCodes::UNKNOWN) @@ -861,6 +868,18 @@ protected function getInvalidAddressUrl(array $error) ], $error)); } + /** + * @return string + */ + protected function getEmptyCartErrorUrl() + { + return $this->dependencyProvider->getRouter()->assemble([ + 'module' => 'frontend', + 'controller' => 'checkout', + 'action' => 'cart', + ]); + } + /** * @return bool */ diff --git a/Controllers/Frontend/Exceptions/EmptyCartException.php b/Controllers/Frontend/Exceptions/EmptyCartException.php new file mode 100644 index 00000000..d7bda279 --- /dev/null +++ b/Controllers/Frontend/Exceptions/EmptyCartException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions; + +use Exception; + +class EmptyCartException extends Exception +{ + public function __construct() + { + parent::__construct('Cannot create a PayPal order with an empty cart'); + } +} diff --git a/Controllers/Frontend/PaypalUnifiedApm.php b/Controllers/Frontend/PaypalUnifiedApm.php index 6af155bd..56ba3732 100644 --- a/Controllers/Frontend/PaypalUnifiedApm.php +++ b/Controllers/Frontend/PaypalUnifiedApm.php @@ -9,6 +9,7 @@ use SwagPaymentPayPalUnified\Components\ErrorCodes; use SwagPaymentPayPalUnified\Components\PayPalOrderParameter\ShopwareOrderData; use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Common\Link; @@ -69,6 +70,14 @@ public function indexAction() } catch (InvalidShippingAddressException $invalidShippingAddressException) { $this->redirectInvalidAddress(['invalidShippingAddress' => true]); + return; + } catch (EmptyCartException $emptyCartException) { + $this->redirect([ + 'module' => 'frontend', + 'controller' => 'checkout', + 'action' => 'cart', + ]); + return; } diff --git a/Controllers/Frontend/PaypalUnifiedV2.php b/Controllers/Frontend/PaypalUnifiedV2.php index 693fb974..e3ed200b 100644 --- a/Controllers/Frontend/PaypalUnifiedV2.php +++ b/Controllers/Frontend/PaypalUnifiedV2.php @@ -9,6 +9,7 @@ use SwagPaymentPayPalUnified\Components\ErrorCodes; use SwagPaymentPayPalUnified\Components\PayPalOrderParameter\ShopwareOrderData; use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InstrumentDeclinedException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; @@ -87,6 +88,11 @@ public function indexAction() $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); $this->view->assign('redirectTo', $this->getInvalidAddressUrl(['invalidShippingAddress' => true])); + return; + } catch (EmptyCartException $emptyCartException) { + $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); + $this->view->assign('redirectTo', $this->getEmptyCartErrorUrl()); + return; } diff --git a/Controllers/Frontend/PaypalUnifiedV2PayUponInvoice.php b/Controllers/Frontend/PaypalUnifiedV2PayUponInvoice.php index cd1c7e7b..ae67bc54 100644 --- a/Controllers/Frontend/PaypalUnifiedV2PayUponInvoice.php +++ b/Controllers/Frontend/PaypalUnifiedV2PayUponInvoice.php @@ -14,6 +14,7 @@ use SwagPaymentPayPalUnified\Components\Exception\PuiValidationException; use SwagPaymentPayPalUnified\Components\PayPalOrderParameter\ShopwareOrderData; use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; use SwagPaymentPayPalUnified\PayPalBundle\PaymentType; @@ -78,6 +79,14 @@ public function indexAction() } catch (InvalidShippingAddressException $invalidShippingAddressException) { $this->redirectInvalidAddress(['invalidShippingAddress' => true]); + return; + } catch (EmptyCartException $emptyCartException) { + $this->redirect([ + 'module' => 'frontend', + 'controller' => 'checkout', + 'action' => 'cart', + ]); + return; } diff --git a/Controllers/Widgets/PaypalUnifiedV2AdvancedCreditDebitCard.php b/Controllers/Widgets/PaypalUnifiedV2AdvancedCreditDebitCard.php index dd4f539f..48bff318 100644 --- a/Controllers/Widgets/PaypalUnifiedV2AdvancedCreditDebitCard.php +++ b/Controllers/Widgets/PaypalUnifiedV2AdvancedCreditDebitCard.php @@ -14,6 +14,7 @@ use SwagPaymentPayPalUnified\Components\Services\ThreeDSecureResultChecker\Exception\ThreeDSecureCardHasNoAuthorization; use SwagPaymentPayPalUnified\Components\Services\ThreeDSecureResultChecker\ThreeDSecureResultChecker; use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InstrumentDeclinedException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; @@ -93,6 +94,11 @@ public function createOrderAction() $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); $this->view->assign('redirectTo', $this->getInvalidAddressUrl(['invalidShippingAddress' => true])); + return; + } catch (EmptyCartException $emptyCartException) { + $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); + $this->view->assign('redirectTo', $this->getEmptyCartErrorUrl()); + return; } diff --git a/Controllers/Widgets/PaypalUnifiedV2SmartPaymentButtons.php b/Controllers/Widgets/PaypalUnifiedV2SmartPaymentButtons.php index efff2aef..092fe017 100644 --- a/Controllers/Widgets/PaypalUnifiedV2SmartPaymentButtons.php +++ b/Controllers/Widgets/PaypalUnifiedV2SmartPaymentButtons.php @@ -9,6 +9,7 @@ use SwagPaymentPayPalUnified\Components\ErrorCodes; use SwagPaymentPayPalUnified\Components\PayPalOrderParameter\ShopwareOrderData; use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController; +use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException; use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException; use SwagPaymentPayPalUnified\PayPalBundle\PaymentType; @@ -70,6 +71,11 @@ public function createOrderAction() $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); $this->view->assign('redirectTo', $this->getInvalidAddressUrl(['invalidShippingAddress' => true])); + return; + } catch (EmptyCartException $emptyCartException) { + $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); + $this->view->assign('redirectTo', $this->getEmptyCartErrorUrl()); + return; } diff --git a/Tests/Functional/Controller/Frontend/AbstractPayPalPaymentControllerThrowsExceptionOnCreateOrderWithEmptyCartTest.php b/Tests/Functional/Controller/Frontend/AbstractPayPalPaymentControllerThrowsExceptionOnCreateOrderWithEmptyCartTest.php new file mode 100644 index 00000000..e664122f --- /dev/null +++ b/Tests/Functional/Controller/Frontend/AbstractPayPalPaymentControllerThrowsExceptionOnCreateOrderWithEmptyCartTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SwagPaymentPayPalUnified\Tests\Functional\Controller\Frontend; + +use Enlight_Controller_Action; +use Enlight_Controller_Response_ResponseHttp; +use Generator; +use Shopware_Controllers_Frontend_PaypalUnifiedApm; +use Shopware_Controllers_Frontend_PaypalUnifiedV2; +use Shopware_Controllers_Frontend_PaypalUnifiedV2PayUponInvoice; +use Shopware_Controllers_Widgets_PaypalUnifiedV2AdvancedCreditDebitCard; +use Shopware_Controllers_Widgets_PaypalUnifiedV2SmartPaymentButtons; +use SwagPaymentPayPalUnified\Tests\Functional\AssertLocationTrait; +use SwagPaymentPayPalUnified\Tests\Functional\ContainerTrait; +use SwagPaymentPayPalUnified\Tests\Functional\ShopRegistrationTrait; +use SwagPaymentPayPalUnified\Tests\Unit\PaypalPaymentControllerTestCase; + +class AbstractPayPalPaymentControllerThrowsExceptionOnCreateOrderWithEmptyCartTest extends PaypalPaymentControllerTestCase +{ + use ContainerTrait; + use ShopRegistrationTrait; + use AssertLocationTrait; + + /** + * @dataProvider createPayPalOrderShouldThrowExceptionWithEmptyCartDataProvider + * + * @param class-string $controllerClass + * @param string $actionName + * + * @return void + */ + public function testCreatePayPalOrderShouldThrowExceptionWithEmptyCart($controllerClass, $actionName, bool $shouldAssignToView) + { + $sOrderVariables = [ + 'sUserData' => require __DIR__ . '/_fixtures/getUser_result.php', + 'sBasket' => ['content' => []], + ]; + + $session = $this->getContainer()->get('session'); + $session->set('sOrderVariables', $sOrderVariables); + + $controller = $this->getController( + $controllerClass, + [ + self::SERVICE_DEPENDENCY_PROVIDER => $this->getContainer()->get('paypal_unified.dependency_provider'), + self::SERVICE_ORDER_PARAMETER_FACADE => $this->getContainer()->get('paypal_unified.paypal_order_parameter_facade'), + ], + null, + new Enlight_Controller_Response_ResponseHttp() + ); + + $controller->$actionName(); + + if ($shouldAssignToView) { + $result = $controller->View()->getAssign('redirectTo'); + + static::assertStringEndsWith('checkout/cart', $result); + + return; + } + + static::assertLocationEndsWith($controller->Response(), 'checkout/cart'); + } + + /** + * @return Generator> + */ + public function createPayPalOrderShouldThrowExceptionWithEmptyCartDataProvider() + { + yield 'Shopware_Controllers_Frontend_PaypalUnifiedV2' => [ + Shopware_Controllers_Frontend_PaypalUnifiedV2::class, + 'indexAction', + true, + ]; + + yield 'Shopware_Controllers_Widgets_PaypalUnifiedV2AdvancedCreditDebitCard' => [ + Shopware_Controllers_Widgets_PaypalUnifiedV2AdvancedCreditDebitCard::class, + 'createOrderAction', + true, + ]; + + yield 'Shopware_Controllers_Widgets_PaypalUnifiedV2SmartPaymentButtons' => [ + Shopware_Controllers_Widgets_PaypalUnifiedV2SmartPaymentButtons::class, + 'createOrderAction', + true, + ]; + + yield 'Shopware_Controllers_Frontend_PaypalUnifiedApm' => [ + Shopware_Controllers_Frontend_PaypalUnifiedApm::class, + 'indexAction', + false, + ]; + + yield 'Shopware_Controllers_Frontend_PaypalUnifiedV2PayUponInvoice' => [ + Shopware_Controllers_Frontend_PaypalUnifiedV2PayUponInvoice::class, + 'indexAction', + false, + ]; + } +} diff --git a/Tests/Functional/Controller/Frontend/PaypalUnifiedV2Test.php b/Tests/Functional/Controller/Frontend/PaypalUnifiedV2Test.php index 8dc7d284..69b63600 100644 --- a/Tests/Functional/Controller/Frontend/PaypalUnifiedV2Test.php +++ b/Tests/Functional/Controller/Frontend/PaypalUnifiedV2Test.php @@ -15,6 +15,9 @@ use Enlight_Components_Session_Namespace as ShopwareSession; use Enlight_Controller_Request_RequestTestCase; use Enlight_Controller_Response_ResponseTestCase; +use Enlight_Controller_Router; +use Enlight_Template_Manager; +use Enlight_View_Default; use Generator; use PHPUnit\Framework\TestCase; use Shopware\Components\BasketSignature\BasketPersister; @@ -76,6 +79,7 @@ public function testIndexErrorHandling( $dependencyProvider = $this->createConfiguredMock(DependencyProvider::class, [ 'getSession' => $session, 'getModule' => new stdClass(), + 'getRouter' => $this->createMock(Enlight_Controller_Router::class), ]); $redirectDataBuilder = $this->createMock(RedirectDataBuilder::class); @@ -131,9 +135,14 @@ public function testIndexErrorHandling( $basketPersister = $this->createMock(BasketPersister::class); } + $payPalOrderParameterMock = $this->createMock(PayPalOrderParameter::class); + if (\is_array($orderData)) { + $payPalOrderParameterMock->method('getCart')->willReturn(\is_array($orderData['sBasket']) ? $orderData['sBasket'] : []); + } + $cartPersister = $this->createMock(CartPersister::class); $orderParameterFacade = $this->createConfiguredMock(PayPalOrderParameterFacadeInterface::class, [ - 'createPayPalOrderParameter' => $this->createMock(PayPalOrderParameter::class), + 'createPayPalOrderParameter' => $payPalOrderParameterMock, ]); $dispatchValidation = $this->createMock(DispatchValidation::class); @@ -191,7 +200,7 @@ public function orderDataProvider() yield 'Exception in OrderBuilder::getOrder should lead to ErrorCodes::UNKNOWN' => [ [ 'sUserData' => [], - 'sBasket' => [], + 'sBasket' => ['content' => [0 => ['anyItem']]], ], false, false, @@ -203,7 +212,7 @@ public function orderDataProvider() yield 'Exception during order creation request should lead to ErrorCodes::COMMUNICATION_FAILURE' => [ [ 'sUserData' => [], - 'sBasket' => [], + 'sBasket' => ['content' => [0 => ['anyItem']]], ], false, false, @@ -213,9 +222,6 @@ public function orderDataProvider() ]; } - /** - * @param Container $container - */ private function getController(Container $container = null) { $request = new Enlight_Controller_Request_RequestTestCase(); @@ -229,6 +235,7 @@ private function getController(Container $container = null) $controller->setRequest($request); $controller->setResponse($response); + $controller->setView(new Enlight_View_Default(new Enlight_Template_Manager())); if ($container instanceof Container) { $controller->setContainer($container); diff --git a/Tests/Functional/Controller/Widgets/PaypalUnifiedV2ExpressCheckoutTest.php b/Tests/Functional/Controller/Widgets/PaypalUnifiedV2ExpressCheckoutTest.php index de43ded8..4c7d14c3 100644 --- a/Tests/Functional/Controller/Widgets/PaypalUnifiedV2ExpressCheckoutTest.php +++ b/Tests/Functional/Controller/Widgets/PaypalUnifiedV2ExpressCheckoutTest.php @@ -184,7 +184,7 @@ public function testPatchAddressAction() private function createOrderParameterFacade() { $facade = $this->createMock(PayPalOrderParameterFacadeInterface::class); - $payPalOrderParameter = new PayPalOrderParameter([], [], PaymentType::PAYPAL_EXPRESS_V2, null, null, 'anyOrderNumber'); + $payPalOrderParameter = new PayPalOrderParameter([], ['content' => ['anyItem' => []]], PaymentType::PAYPAL_EXPRESS_V2, null, null, 'anyOrderNumber'); $facade->method('createPayPalOrderParameter')->willReturn($payPalOrderParameter); return $facade;