From 10b407b4f3564125e110e378d02bf07214b2e06b Mon Sep 17 00:00:00 2001 From: Damian Pastorini Date: Wed, 20 Apr 2022 13:03:09 +0200 Subject: [PATCH 01/15] ASW-502 - Confirmation modal --- .../js/jquery.adyen-disable-payment.js | 39 +++++++++++++++++++ Resources/frontend/less/all.less | 9 +++++ .../frontend/register/payment_fieldset.tpl | 17 ++++++++ 3 files changed, 65 insertions(+) diff --git a/Resources/frontend/js/jquery.adyen-disable-payment.js b/Resources/frontend/js/jquery.adyen-disable-payment.js index 8bdf54a8..0be5f6c5 100644 --- a/Resources/frontend/js/jquery.adyen-disable-payment.js +++ b/Resources/frontend/js/jquery.adyen-disable-payment.js @@ -23,6 +23,21 @@ * CSS classes selector to clear the error elements */ errorClassSelector: '.alert.is--error.is--rounded.is--adyen-error', + /** + * @var string modalSelector + * CSS classes selector to use as confirmation modal content. + */ + modalSelector: '.adyenDisableTokenConfirmationModal', + /** + * @var string modalConfirmButtonSelector + * CSS classes selector for the disable-confirm button + */ + modalConfirmButtonSelector: '.disableConfirm', + /** + * @var string modalCancelButtonSelector + * CSS classes selector for the disable-cancel button + */ + modalCancelButtonSelector: '.disableCancel', /** * @var string errorMessageClass * CSS classes for the error message element @@ -32,6 +47,7 @@ init: function () { var me = this; me.applyDataAttributes(); + me.modalContent = $(me.opts.modalSelector).html() ?? ''; me.$el.on('click', $.proxy(me.enableDisableButtonClick, me)); }, enableDisableButtonClick: function () { @@ -39,6 +55,29 @@ if (0 === me.opts.adyenStoredMethodId.length) { return; } + if('' === me.modalContent){ + return; + } + me.modal = $.modal.open(me.modalContent, { + showCloseButton: false, + closeOnOverlay: false, + additionalClass: 'adyen-modal disable-token-confirmation' + }); + me.buttonConfirm = $(me.opts.modalConfirmButtonSelector); + me.buttonConfirm.on('click', $.proxy(me.runDisableTokenCall, me)); + me.buttonCancel = $(me.opts.modalCancelButtonSelector); + me.buttonCancel.on('click', $.proxy(me.closeModal, me)); + }, + closeModal: function () { + var me = this; + if(!me.modal){ + return; + } + me.modal.close(); + }, + runDisableTokenCall: function () { + var me = this; + me.closeModal(); $.loadingIndicator.open(); $.post({ url: me.opts.adyenDisableTokenUrl, diff --git a/Resources/frontend/less/all.less b/Resources/frontend/less/all.less index 6df53d6b..59335bb1 100644 --- a/Resources/frontend/less/all.less +++ b/Resources/frontend/less/all.less @@ -27,6 +27,15 @@ .adyen-modal .content { padding: 20px; + + &.disable-token-confirmation { + max-height: 12em; + + .buttons-container { + display: flex; + justify-content: space-evenly; + } + } } .alert.is--adyen-error { diff --git a/Resources/views/frontend/register/payment_fieldset.tpl b/Resources/views/frontend/register/payment_fieldset.tpl index 795d6fe9..db1aaa15 100644 --- a/Resources/views/frontend/register/payment_fieldset.tpl +++ b/Resources/views/frontend/register/payment_fieldset.tpl @@ -34,3 +34,20 @@ {/if} {/block} {/block} + +{block name="frontend_register_payment_fieldset"} + {$smarty.block.parent} +
+
+

{s name='adyenDisableTokenConfirmationMessage'}Are you sure to remove the stored payment method?{/s}

+
+ + +
+
+
+{/block} From 5541bb0eda7d934c70767c03ad2ac5157106cf00 Mon Sep 17 00:00:00 2001 From: Damian Pastorini Date: Wed, 20 Apr 2022 13:04:41 +0200 Subject: [PATCH 02/15] ASW-502 - Styles fix --- Resources/frontend/less/all.less | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/frontend/less/all.less b/Resources/frontend/less/all.less index 59335bb1..6dbdccc8 100644 --- a/Resources/frontend/less/all.less +++ b/Resources/frontend/less/all.less @@ -27,14 +27,14 @@ .adyen-modal .content { padding: 20px; +} - &.disable-token-confirmation { - max-height: 12em; +.disable-token-confirmation { + max-height: 12em; - .buttons-container { - display: flex; - justify-content: space-evenly; - } + .buttons-container { + display: flex; + justify-content: space-evenly; } } From 9186201c61752dbd2a9801db5b940e3c18510e2c Mon Sep 17 00:00:00 2001 From: Damian Pastorini Date: Fri, 22 Apr 2022 09:40:11 +0200 Subject: [PATCH 03/15] ASW-502 - Confirmation modal --- .../Frontend/DisableRecurringToken.php | 21 +++++++++++++------ .../js/jquery.adyen-disable-payment.js | 13 ++++++++---- Resources/frontend/less/all.less | 12 ++++++++++- .../frontend/register/payment_fieldset.tpl | 1 + 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Controllers/Frontend/DisableRecurringToken.php b/Controllers/Frontend/DisableRecurringToken.php index 9ff33d1c..370f89fb 100644 --- a/Controllers/Frontend/DisableRecurringToken.php +++ b/Controllers/Frontend/DisableRecurringToken.php @@ -15,11 +15,13 @@ class Shopware_Controllers_Frontend_DisableRecurringToken extends Enlight_Contro { private ApiJsonResponse $frontendJsonResponse; private DisableTokenRequestHandlerInterface $disableTokenRequestHandler; + private Shopware_Components_Snippet_Manager $snippets; public function preDispatch(): void { $this->frontendJsonResponse = $this->get(FrontendJsonResponse::class); $this->disableTokenRequestHandler = $this->get(DisableTokenRequestHandler::class); + $this->snippets = $this->get('snippets'); } public function disabledAction(): void @@ -29,7 +31,11 @@ public function disabledAction(): void $this->frontendJsonResponse->sendJsonBadRequestResponse( $this->Front(), $this->Response(), - 'Invalid method.' + $this->snippets->getNamespace('adyen/checkout/error')->get( + 'disableTokenInvalidMethodMessage', + 'Invalid method.', + true + ) ); return; @@ -40,7 +46,11 @@ public function disabledAction(): void $this->frontendJsonResponse->sendJsonBadRequestResponse( $this->Front(), $this->Response(), - 'Missing recurring token param.' + $this->snippets->getNamespace('adyen/checkout/error')->get( + 'disableTokenMissingRecurringTokenMessage', + 'Missing recurring token param.', + true + ) ); return; @@ -48,12 +58,11 @@ public function disabledAction(): void $result = $this->disableTokenRequestHandler->disableToken($recurringToken, Shopware()->Shop()); if (!$result->isSuccess()) { - $this->frontendJsonResponse->sendJsonResponse( + $this->frontendJsonResponse->sendJsonBadRequestResponse( $this->Front(), $this->Response(), - JsonResponse::create( - ['error' => true, 'message' => $result->message()], - Response::HTTP_BAD_REQUEST + $this->snippets->getNamespace('adyen/checkout/error')->get( + $result->message() ) ); diff --git a/Resources/frontend/js/jquery.adyen-disable-payment.js b/Resources/frontend/js/jquery.adyen-disable-payment.js index 0be5f6c5..a5d15993 100644 --- a/Resources/frontend/js/jquery.adyen-disable-payment.js +++ b/Resources/frontend/js/jquery.adyen-disable-payment.js @@ -42,7 +42,12 @@ * @var string errorMessageClass * CSS classes for the error message element */ - errorMessageClass: 'alert--content' + errorMessageClass: 'alert--content', + /** + * @var string modalErrorContainerSelector + * CSS classes for the error message container in the modal + */ + modalErrorContainerSelector: '.modal-error-container', }, init: function () { var me = this; @@ -59,7 +64,7 @@ return; } me.modal = $.modal.open(me.modalContent, { - showCloseButton: false, + showCloseButton: true, closeOnOverlay: false, additionalClass: 'adyen-modal disable-token-confirmation' }); @@ -77,8 +82,8 @@ }, runDisableTokenCall: function () { var me = this; - me.closeModal(); $.loadingIndicator.open(); + $.loadingIndicator.loader.$loader.addClass('over-modal'); $.post({ url: me.opts.adyenDisableTokenUrl, dataType: 'json', @@ -97,7 +102,7 @@ $(me.opts.errorClassSelector).remove(); var error = $('
').addClass(me.opts.errorClass); error.append($('
').addClass(me.opts.errorMessageClass).html(message)); - me.$el.parent().append(error); + $(me.opts.modalErrorContainerSelector).append(error); } }); })(jQuery); diff --git a/Resources/frontend/less/all.less b/Resources/frontend/less/all.less index 6dbdccc8..cab1f7fd 100644 --- a/Resources/frontend/less/all.less +++ b/Resources/frontend/less/all.less @@ -27,14 +27,24 @@ .adyen-modal .content { padding: 20px; + + .modal-error-container { + margin-top: 2em; + } +} + +.over-modal { + z-index: 7000; } .disable-token-confirmation { - max-height: 12em; + height: auto; + max-height: 18em; .buttons-container { display: flex; justify-content: space-evenly; + margin-top: 2em; } } diff --git a/Resources/views/frontend/register/payment_fieldset.tpl b/Resources/views/frontend/register/payment_fieldset.tpl index db1aaa15..412bc07a 100644 --- a/Resources/views/frontend/register/payment_fieldset.tpl +++ b/Resources/views/frontend/register/payment_fieldset.tpl @@ -48,6 +48,7 @@ {s name='adyenDisableTokenConfirmationButtonCancel'}Cancel{/s}
+
{/block} From 0aa6cfeb825d17a991332484babd791c9e65b20d Mon Sep 17 00:00:00 2001 From: davebueds Date: Wed, 4 May 2022 14:45:10 +0200 Subject: [PATCH 04/15] ASW-502 rework null check --- Resources/frontend/js/jquery.adyen-disable-payment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/frontend/js/jquery.adyen-disable-payment.js b/Resources/frontend/js/jquery.adyen-disable-payment.js index a5d15993..b815f5f1 100644 --- a/Resources/frontend/js/jquery.adyen-disable-payment.js +++ b/Resources/frontend/js/jquery.adyen-disable-payment.js @@ -52,7 +52,7 @@ init: function () { var me = this; me.applyDataAttributes(); - me.modalContent = $(me.opts.modalSelector).html() ?? ''; + me.modalContent = $(me.opts.modalSelector).html() || ''; me.$el.on('click', $.proxy(me.enableDisableButtonClick, me)); }, enableDisableButtonClick: function () { From e0d433421df32491268123b25df931e6efde58e0 Mon Sep 17 00:00:00 2001 From: davebueds Date: Tue, 26 Apr 2022 11:29:07 +0200 Subject: [PATCH 05/15] ASW-335 add duplicate notifications check --- Components/NotificationManager.php | 20 +++++++++ Components/NotificationProcessor.php | 44 ++++++++++++------- Exceptions/DuplicateNotificationException.php | 29 ++++++++++++ 3 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 Exceptions/DuplicateNotificationException.php diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index fa012c17..cb88ff5c 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -98,4 +98,24 @@ public function getLastNotificationForPspReference(string $pspReference) return; } } + + public function getUniqueNotification(Notification $notification): Notification + { + $uniqueNotification = $this->notificationRepository->findOneBy([ + 'orderId' => $notification->getOrderId(), + 'pspReference' => $notification->getPspReference(), + 'paymentMethod' => $notification->getPaymentMethod(), + 'success' => $notification->isSuccess(), + 'eventCode' => $notification->getEventCode(), + 'merchantAccountCode' => $notification->getMerchantAccountCode(), + 'amountValue' => $notification->getAmountValue(), + 'amountCurrency' => $notification->getAmountCurrency(), + ]); + + if (!($uniqueNotification instanceof Notification)) { + throw new NoResultException(); + } + + return $uniqueNotification; + } } diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index fc4274f1..31fda64f 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -5,6 +5,7 @@ namespace AdyenPayment\Components; use AdyenPayment\Components\NotificationProcessor\NotificationProcessorInterface; +use AdyenPayment\Exceptions\DuplicateNotificationException; use AdyenPayment\Exceptions\NoNotificationProcessorFoundException; use AdyenPayment\Exceptions\OrderNotFoundException; use AdyenPayment\Models\Enum\NotificationStatus; @@ -12,6 +13,7 @@ use AdyenPayment\Models\Feedback\NotificationProcessorFeedback; use AdyenPayment\Models\Notification; use AdyenPayment\Models\NotificationException; +use Doctrine\ORM\NoResultException; use Psr\Log\LoggerInterface; use Shopware\Components\ContainerAwareEventManager; use Shopware\Components\Model\ModelManager; @@ -26,21 +28,10 @@ class NotificationProcessor * @var NotificationProcessorInterface[] */ private $processors; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var ModelManager - */ - private $modelManager; - - /** - * @var ContainerAwareEventManager - */ - private $eventManager; + private LoggerInterface $logger; + private ModelManager $modelManager; + private ContainerAwareEventManager $eventManager; + private NotificationManager $notificationManager; /** * NotificationProcessor constructor. @@ -50,11 +41,13 @@ class NotificationProcessor public function __construct( LoggerInterface $logger, ModelManager $modelManager, - ContainerAwareEventManager $eventManager + ContainerAwareEventManager $eventManager, + NotificationManager $notificationManager ) { $this->logger = $logger; $this->modelManager = $modelManager; $this->eventManager = $eventManager; + $this->notificationManager = $notificationManager; } /** @@ -68,6 +61,10 @@ public function processMany(Traversable $notifications): \Generator foreach ($notifications as $notification) { try { yield from $this->process($notification); + } catch (DuplicateNotificationException $exception) { + $this->logger->notice( + 'Duplicate notification', ['message' => $exception->getMessage()] + ); } catch (NoNotificationProcessorFoundException $exception) { $this->logger->notice( 'No notification processor found', @@ -105,6 +102,10 @@ public function processMany(Traversable $notifications): \Generator */ private function process(Notification $notification): \Generator { + if ($this->isNotificationDuplicate($notification)) { + throw DuplicateNotificationException::withNotification($notification); + } + $processors = $this->findProcessors($notification); if (empty($processors)) { @@ -181,4 +182,15 @@ private function findProcessors(Notification $notification): array return $processors; } + + private function isNotificationDuplicate(Notification $notification): bool + { + try { + $this->notificationManager->getUniqueNotification($notification); + + return true; + } catch (NoResultException $exception) { + return false; + } + } } diff --git a/Exceptions/DuplicateNotificationException.php b/Exceptions/DuplicateNotificationException.php new file mode 100644 index 00000000..01a495b7 --- /dev/null +++ b/Exceptions/DuplicateNotificationException.php @@ -0,0 +1,29 @@ +getId(), + $notification->getOrderId(), + $notification->getPspReference(), + $notification->getStatus(), + $notification->getPaymentMethod(), + $notification->getEventCode(), + $notification->isSuccess(), + $notification->getMerchantAccountCode(), + $notification->getAmountValue(), + $notification->getAmountCurrency() + )); + } +} From 6d70904d59a1ce0e9dc914eb06b27bbde5c48e5b Mon Sep 17 00:00:00 2001 From: davebueds Date: Tue, 26 Apr 2022 11:49:07 +0200 Subject: [PATCH 06/15] ASW-335 duplicate notification exception in const --- Components/NotificationManager.php | 2 +- Components/NotificationProcessor.php | 3 ++- Exceptions/DuplicateNotificationException.php | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index cb88ff5c..e7c31fed 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -112,7 +112,7 @@ public function getUniqueNotification(Notification $notification): Notification 'amountCurrency' => $notification->getAmountCurrency(), ]); - if (!($uniqueNotification instanceof Notification)) { + if (!$uniqueNotification instanceof Notification) { throw new NoResultException(); } diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index 31fda64f..6aaf39f5 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -63,7 +63,8 @@ public function processMany(Traversable $notifications): \Generator yield from $this->process($notification); } catch (DuplicateNotificationException $exception) { $this->logger->notice( - 'Duplicate notification', ['message' => $exception->getMessage()] + DuplicateNotificationException::DUPLICATE_NOTIFICATION_NOT_HANDLED, + ['message' => $exception->getMessage()] ); } catch (NoNotificationProcessorFoundException $exception) { $this->logger->notice( diff --git a/Exceptions/DuplicateNotificationException.php b/Exceptions/DuplicateNotificationException.php index 01a495b7..377de454 100644 --- a/Exceptions/DuplicateNotificationException.php +++ b/Exceptions/DuplicateNotificationException.php @@ -8,11 +8,13 @@ final class DuplicateNotificationException extends \RunTimeException { + public const DUPLICATE_NOTIFICATION_NOT_HANDLED = 'Duplicate notification is not handled.'; + public static function withNotification(Notification $notification): self { return new self(sprintf( - 'Duplicate notification is not handled. - Notification with id: "%s", orderId: "%s", pspReference: "%s", status: "%s", paymentMethod: "%s", + self::DUPLICATE_NOTIFICATION_NOT_HANDLED. + 'Notification with id: "%s", orderId: "%s", pspReference: "%s", status: "%s", paymentMethod: "%s", eventCode: "%s", success: "%s", merchantAccountCode: "%s", amountValue: "%s", amountCurrency: "%s"', $notification->getId(), $notification->getOrderId(), From ce3a4c3d39448f1440a6a8b4e96c16432161927e Mon Sep 17 00:00:00 2001 From: davebueds Date: Tue, 26 Apr 2022 13:59:34 +0200 Subject: [PATCH 07/15] ASW-335 remove isDuplicate method --- Components/NotificationManager.php | 6 +++--- Components/NotificationProcessor.php | 14 +------------- Resources/services/components.xml | 1 + 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index e7c31fed..6b834c04 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -99,7 +99,7 @@ public function getLastNotificationForPspReference(string $pspReference) } } - public function getUniqueNotification(Notification $notification): Notification + public function notificationExists(Notification $notification): bool { $uniqueNotification = $this->notificationRepository->findOneBy([ 'orderId' => $notification->getOrderId(), @@ -113,9 +113,9 @@ public function getUniqueNotification(Notification $notification): Notification ]); if (!$uniqueNotification instanceof Notification) { - throw new NoResultException(); + return false; } - return $uniqueNotification; + return true; } } diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index 6aaf39f5..ce820d20 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -13,7 +13,6 @@ use AdyenPayment\Models\Feedback\NotificationProcessorFeedback; use AdyenPayment\Models\Notification; use AdyenPayment\Models\NotificationException; -use Doctrine\ORM\NoResultException; use Psr\Log\LoggerInterface; use Shopware\Components\ContainerAwareEventManager; use Shopware\Components\Model\ModelManager; @@ -103,7 +102,7 @@ public function processMany(Traversable $notifications): \Generator */ private function process(Notification $notification): \Generator { - if ($this->isNotificationDuplicate($notification)) { + if (!$this->notificationManager->notificationExists($notification)) { throw DuplicateNotificationException::withNotification($notification); } @@ -183,15 +182,4 @@ private function findProcessors(Notification $notification): array return $processors; } - - private function isNotificationDuplicate(Notification $notification): bool - { - try { - $this->notificationManager->getUniqueNotification($notification); - - return true; - } catch (NoResultException $exception) { - return false; - } - } } diff --git a/Resources/services/components.xml b/Resources/services/components.xml index 1512826a..c4d2492a 100644 --- a/Resources/services/components.xml +++ b/Resources/services/components.xml @@ -10,6 +10,7 @@ + From e0c691ab1db93ff386e761237278f7026729753a Mon Sep 17 00:00:00 2001 From: davebueds Date: Mon, 2 May 2022 15:53:46 +0200 Subject: [PATCH 08/15] ASW-335 add duplicated notification handling on text notifications as well --- Components/Builder/NotificationBuilder.php | 1 + Components/IncomingNotificationManager.php | 37 ++++++++++++---------- Components/NotificationManager.php | 13 ++++---- Resources/services/managers.xml | 1 + Resources/services/validators.xml | 2 +- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/Components/Builder/NotificationBuilder.php b/Components/Builder/NotificationBuilder.php index 978d717e..b47ef3bb 100644 --- a/Components/Builder/NotificationBuilder.php +++ b/Components/Builder/NotificationBuilder.php @@ -68,6 +68,7 @@ public function fromParams($params): Notification } $notification->setOrder($order); + $notification->setOrderId($order->getId()); if (isset($params['pspReference'])) { $notification->setPspReference($params['pspReference']); diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index b1ad831a..5bbe12b5 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -5,9 +5,11 @@ namespace AdyenPayment\Components; use AdyenPayment\Components\Builder\NotificationBuilder; +use AdyenPayment\Exceptions\DuplicateNotificationException; use AdyenPayment\Exceptions\InvalidParameterException; use AdyenPayment\Exceptions\OrderNotFoundException; use AdyenPayment\Models\Feedback\TextNotificationItemFeedback; +use AdyenPayment\Models\Notification; use AdyenPayment\Models\TextNotification; use Doctrine\ORM\ORMException; use Psr\Log\LoggerInterface; @@ -18,20 +20,10 @@ */ class IncomingNotificationManager { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var NotificationBuilder - */ - private $notificationBuilder; - - /** - * @var ModelManager - */ - private $entityManager; + private LoggerInterface $logger; + private NotificationBuilder $notificationBuilder; + private ModelManager $entityManager; + private NotificationManager $notificationManager; /** * IncomingNotificationManager constructor. @@ -39,11 +31,13 @@ class IncomingNotificationManager public function __construct( LoggerInterface $logger, NotificationBuilder $notificationBuilder, - ModelManager $entityManager + ModelManager $entityManager, + NotificationManager $notificationManager ) { $this->logger = $logger; $this->notificationBuilder = $notificationBuilder; $this->entityManager = $entityManager; + $this->notificationManager = $notificationManager; } /** @@ -60,6 +54,12 @@ public function convertNotifications(array $textNotifications): void $notification = $this->notificationBuilder->fromParams( json_decode($textNotificationItem->getTextNotification(), true) ); + + $existingNotification = $this->notificationManager->fetchNotification($notification); + if ($existingNotification instanceof Notification) { + throw DuplicateNotificationException::withNotification($existingNotification); + } + $this->entityManager->persist($notification); } } catch (InvalidParameterException $exception) { @@ -70,10 +70,15 @@ public function convertNotifications(array $textNotifications): void $this->logger->warning( $exception->getMessage().' '.$textNotificationItem->getTextNotification() ); + } catch (DuplicateNotificationException $exception) { + $this->logger->notice( + DuplicateNotificationException::DUPLICATE_NOTIFICATION_NOT_HANDLED, + ['message' => $exception->getMessage()] + ); } $this->entityManager->remove($textNotificationItem); + $this->entityManager->flush(); } - $this->entityManager->flush(); } /** diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index 6b834c04..e6361d69 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -101,7 +101,12 @@ public function getLastNotificationForPspReference(string $pspReference) public function notificationExists(Notification $notification): bool { - $uniqueNotification = $this->notificationRepository->findOneBy([ + return $this->fetchNotification($notification) instanceof Notification; + } + + public function fetchNotification(Notification $notification): ?Notification + { + return $this->notificationRepository->findOneBy([ 'orderId' => $notification->getOrderId(), 'pspReference' => $notification->getPspReference(), 'paymentMethod' => $notification->getPaymentMethod(), @@ -111,11 +116,5 @@ public function notificationExists(Notification $notification): bool 'amountValue' => $notification->getAmountValue(), 'amountCurrency' => $notification->getAmountCurrency(), ]); - - if (!$uniqueNotification instanceof Notification) { - return false; - } - - return true; } } diff --git a/Resources/services/managers.xml b/Resources/services/managers.xml index dfbd4728..788e9ebd 100644 --- a/Resources/services/managers.xml +++ b/Resources/services/managers.xml @@ -20,6 +20,7 @@ + diff --git a/Resources/services/validators.xml b/Resources/services/validators.xml index 4311d685..07c15785 100644 --- a/Resources/services/validators.xml +++ b/Resources/services/validators.xml @@ -30,4 +30,4 @@ service('models').getRepository('Shopware\\Models\\Shop\\Shop') - \ No newline at end of file + From 6be2f9b2e1ff0fe7faee39dfa5b1e038bd55c7d3 Mon Sep 17 00:00:00 2001 From: davebueds Date: Tue, 3 May 2022 14:24:53 +0200 Subject: [PATCH 09/15] ASW-335 rework CR --- Components/IncomingNotificationManager.php | 9 +--- Components/NotificationManager.php | 11 +++++ Components/NotificationProcessor.php | 7 +-- Exceptions/DuplicateNotificationException.php | 7 +-- .../DuplicateNotificationExceptionTest.php | 49 +++++++++++++++++++ 5 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index 5bbe12b5..a8c15ce5 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -9,7 +9,6 @@ use AdyenPayment\Exceptions\InvalidParameterException; use AdyenPayment\Exceptions\OrderNotFoundException; use AdyenPayment\Models\Feedback\TextNotificationItemFeedback; -use AdyenPayment\Models\Notification; use AdyenPayment\Models\TextNotification; use Doctrine\ORM\ORMException; use Psr\Log\LoggerInterface; @@ -55,10 +54,7 @@ public function convertNotifications(array $textNotifications): void json_decode($textNotificationItem->getTextNotification(), true) ); - $existingNotification = $this->notificationManager->fetchNotification($notification); - if ($existingNotification instanceof Notification) { - throw DuplicateNotificationException::withNotification($existingNotification); - } + $this->notificationManager->isDuplicate($notification); $this->entityManager->persist($notification); } @@ -72,8 +68,7 @@ public function convertNotifications(array $textNotifications): void ); } catch (DuplicateNotificationException $exception) { $this->logger->notice( - DuplicateNotificationException::DUPLICATE_NOTIFICATION_NOT_HANDLED, - ['message' => $exception->getMessage()] + $exception->getMessage() ); } $this->entityManager->remove($textNotificationItem); diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index e6361d69..0de81feb 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -4,6 +4,7 @@ namespace AdyenPayment\Components; +use AdyenPayment\Exceptions\DuplicateNotificationException; use AdyenPayment\Models\Enum\NotificationStatus; use AdyenPayment\Models\Notification; use Doctrine\ORM\EntityRepository; @@ -104,6 +105,16 @@ public function notificationExists(Notification $notification): bool return $this->fetchNotification($notification) instanceof Notification; } + public function isDuplicate(Notification $notification): bool + { + $record = $this->fetchNotification($notification); + if ($record instanceof Notification) { + throw DuplicateNotificationException::withNotification($record); + } + + return $record instanceof Notification; + } + public function fetchNotification(Notification $notification): ?Notification { return $this->notificationRepository->findOneBy([ diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index ce820d20..01d80461 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -62,8 +62,7 @@ public function processMany(Traversable $notifications): \Generator yield from $this->process($notification); } catch (DuplicateNotificationException $exception) { $this->logger->notice( - DuplicateNotificationException::DUPLICATE_NOTIFICATION_NOT_HANDLED, - ['message' => $exception->getMessage()] + $exception->getMessage() ); } catch (NoNotificationProcessorFoundException $exception) { $this->logger->notice( @@ -102,9 +101,7 @@ public function processMany(Traversable $notifications): \Generator */ private function process(Notification $notification): \Generator { - if (!$this->notificationManager->notificationExists($notification)) { - throw DuplicateNotificationException::withNotification($notification); - } + $this->notificationManager->isDuplicate($notification); $processors = $this->findProcessors($notification); diff --git a/Exceptions/DuplicateNotificationException.php b/Exceptions/DuplicateNotificationException.php index 377de454..8edf5474 100644 --- a/Exceptions/DuplicateNotificationException.php +++ b/Exceptions/DuplicateNotificationException.php @@ -8,14 +8,11 @@ final class DuplicateNotificationException extends \RunTimeException { - public const DUPLICATE_NOTIFICATION_NOT_HANDLED = 'Duplicate notification is not handled.'; - public static function withNotification(Notification $notification): self { return new self(sprintf( - self::DUPLICATE_NOTIFICATION_NOT_HANDLED. - 'Notification with id: "%s", orderId: "%s", pspReference: "%s", status: "%s", paymentMethod: "%s", - eventCode: "%s", success: "%s", merchantAccountCode: "%s", amountValue: "%s", amountCurrency: "%s"', + 'Duplicate notification is not handled. '. + 'Notification with id: "%s", orderId: "%s", pspReference: "%s", status: "%s", paymentMethod: "%s", eventCode: "%s", success: "%s", merchantAccountCode: "%s", amountValue: "%s", amountCurrency: "%s"', $notification->getId(), $notification->getOrderId(), $notification->getPspReference(), diff --git a/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php b/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php new file mode 100644 index 00000000..9b1231ce --- /dev/null +++ b/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php @@ -0,0 +1,49 @@ +exception = new DuplicateNotificationException(); + } + + /** @test */ + public function is_a_runtime_exception(): void + { + self::assertInstanceOf(\RuntimeException::class, $this->exception); + } + + /** @test */ + public function it_can_be_constructed_with_a_notification(): void + { + $notification = new Notification(); + $notification + ->setId($id = 1) + ->setOrderId($orderId = 2) + ->setPspReference($pspReference = 'PSP_REF_1') + ->setStatus('received') + ->setPaymentMethod('mc') + ->setEventCode('AUTHORISATION') + ->setSuccess(true) + ->setMerchantAccountCode('Adyen-test') + ->setAmountValue(4598.0000) + ->setAmountCurrency('EUR'); + + $exception = DuplicateNotificationException::withNotification($notification); + + self::assertInstanceOf(DuplicateNotificationException::class, $exception); + self::assertEquals('Duplicate notification is not handled. Notification with id: "1", orderId: "2", pspReference: "PSP_REF_1", status: "received", paymentMethod: "mc", eventCode: "AUTHORISATION", success: "1", merchantAccountCode: "Adyen-test", amountValue: "4598", amountCurrency: "EUR"', + $exception->getMessage() + ); + } +} From 754974805f3466832a7ed8b3269f067f59c4cd55 Mon Sep 17 00:00:00 2001 From: davebueds Date: Wed, 4 May 2022 10:43:28 +0200 Subject: [PATCH 10/15] ASW-335 rework so it uses specification pattern on guardDuplicate notification --- Components/IncomingNotificationManager.php | 2 +- Components/NotificationManager.php | 23 ++++++---------------- Components/NotificationProcessor.php | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index a8c15ce5..e098955a 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -54,7 +54,7 @@ public function convertNotifications(array $textNotifications): void json_decode($textNotificationItem->getTextNotification(), true) ); - $this->notificationManager->isDuplicate($notification); + $this->notificationManager->guardDuplicate($notification); $this->entityManager->persist($notification); } diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index 0de81feb..b4ecf8f9 100644 --- a/Components/NotificationManager.php +++ b/Components/NotificationManager.php @@ -100,24 +100,9 @@ public function getLastNotificationForPspReference(string $pspReference) } } - public function notificationExists(Notification $notification): bool + public function guardDuplicate(Notification $notification): void { - return $this->fetchNotification($notification) instanceof Notification; - } - - public function isDuplicate(Notification $notification): bool - { - $record = $this->fetchNotification($notification); - if ($record instanceof Notification) { - throw DuplicateNotificationException::withNotification($record); - } - - return $record instanceof Notification; - } - - public function fetchNotification(Notification $notification): ?Notification - { - return $this->notificationRepository->findOneBy([ + $record = $this->notificationRepository->findOneBy([ 'orderId' => $notification->getOrderId(), 'pspReference' => $notification->getPspReference(), 'paymentMethod' => $notification->getPaymentMethod(), @@ -127,5 +112,9 @@ public function fetchNotification(Notification $notification): ?Notification 'amountValue' => $notification->getAmountValue(), 'amountCurrency' => $notification->getAmountCurrency(), ]); + + if ($record instanceof Notification) { + throw DuplicateNotificationException::withNotification($record); + } } } diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index 01d80461..7268efd9 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -101,7 +101,7 @@ public function processMany(Traversable $notifications): \Generator */ private function process(Notification $notification): \Generator { - $this->notificationManager->isDuplicate($notification); + $this->notificationManager->guardDuplicate($notification); $processors = $this->findProcessors($notification); From cdd2c894a70b4213b3443c0546847f254acbda4a Mon Sep 17 00:00:00 2001 From: davebueds Date: Wed, 4 May 2022 10:18:13 +0200 Subject: [PATCH 11/15] ASW-437 add checkout basket provider which uses checkout controller get basket --- Resources/services/shopware.xml | 4 +++ Resources/services/subscribers.xml | 1 + Shopware/Provider/CheckoutBasketProvider.php | 26 +++++++++++++++++++ .../CheckoutBasketProviderInterface.php | 10 +++++++ Subscriber/CheckoutSubscriber.php | 9 +++++-- 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Shopware/Provider/CheckoutBasketProvider.php create mode 100644 Shopware/Provider/CheckoutBasketProviderInterface.php diff --git a/Resources/services/shopware.xml b/Resources/services/shopware.xml index 490cb309..d53aae49 100644 --- a/Resources/services/shopware.xml +++ b/Resources/services/shopware.xml @@ -7,5 +7,9 @@ + + + + diff --git a/Resources/services/subscribers.xml b/Resources/services/subscribers.xml index a05c526f..3bd2d050 100644 --- a/Resources/services/subscribers.xml +++ b/Resources/services/subscribers.xml @@ -66,6 +66,7 @@ + diff --git a/Shopware/Provider/CheckoutBasketProvider.php b/Shopware/Provider/CheckoutBasketProvider.php new file mode 100644 index 00000000..48562b8d --- /dev/null +++ b/Shopware/Provider/CheckoutBasketProvider.php @@ -0,0 +1,26 @@ +container = $container; + $this->view = new \Enlight_View_Default($engine); + $this->init(); + + parent::__construct(); + } + + public function __invoke($mergeProportional = true): array + { + return $this->getBasket($mergeProportional); + } +} diff --git a/Shopware/Provider/CheckoutBasketProviderInterface.php b/Shopware/Provider/CheckoutBasketProviderInterface.php new file mode 100644 index 00000000..3014f088 --- /dev/null +++ b/Shopware/Provider/CheckoutBasketProviderInterface.php @@ -0,0 +1,10 @@ +configuration = $configuration; $this->paymentMethodService = $paymentMethodService; @@ -37,6 +40,7 @@ public function __construct( $this->enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; $this->paymentMethodOptionsBuilder = $paymentMethodOptionsBuilder; $this->paymentMeanCollectionSerializer = $paymentMeanCollectionSerializer; + $this->checkoutBasketProvider = $checkoutBasketProvider; } public static function getSubscribedEvents(): array @@ -63,7 +67,8 @@ private function checkBasketAmount(\Enlight_Controller_Action $subject): void return; } - $basket = $subject->View()->sBasket; + $basket = ($this->checkoutBasketProvider)(); + if (!$basket) { return; } From 9c5d8ff8b1b95e255f15863ac7c268671556c3d1 Mon Sep 17 00:00:00 2001 From: davebueds Date: Fri, 22 Apr 2022 14:15:42 +0200 Subject: [PATCH 12/15] ASW-535 additional description --- Enricher/Payment/PaymentMethodEnricher.php | 12 +++++------- .../Enricher/Payment/PaymentMethodEnricherTest.php | 9 ++++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Enricher/Payment/PaymentMethodEnricher.php b/Enricher/Payment/PaymentMethodEnricher.php index 21b67139..130b404f 100644 --- a/Enricher/Payment/PaymentMethodEnricher.php +++ b/Enricher/Payment/PaymentMethodEnricher.php @@ -26,7 +26,7 @@ public function __invoke(array $shopwareMethod, PaymentMethod $paymentMethod): a { return array_merge($shopwareMethod, [ 'enriched' => true, - 'additionaldescription' => $this->enrichAdditionalDescription($paymentMethod), + 'additionaldescription' => $this->enrichAdditionalDescription($shopwareMethod, $paymentMethod), 'image' => $this->imageLogoProvider->provideByType($paymentMethod->adyenType()->type()), 'isStoredPayment' => $paymentMethod->isStoredPayment(), 'isAdyenPaymentMethod' => true, @@ -37,16 +37,14 @@ public function __invoke(array $shopwareMethod, PaymentMethod $paymentMethod): a ); } - private function enrichAdditionalDescription(PaymentMethod $adyenMethod): string + private function enrichAdditionalDescription(array $shopwareMethod, PaymentMethod $adyenMethod): string { - $description = $this->snippets - ->getNamespace('adyen/method/description') - ->get($adyenMethod->adyenType()->type()) ?? ''; - if (!$adyenMethod->isStoredPayment()) { - return $description; + return $shopwareMethod['additionaldescription'] ?? ''; } + $description = $shopwareMethod['additionaldescription'] ?? ''; + return sprintf( '%s%s: %s', ($description ? $description.' ' : ''), diff --git a/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php b/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php index d46adb1d..0ecade90 100644 --- a/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php +++ b/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php @@ -47,7 +47,7 @@ public function it_will_enrich_a_payment_method_without_stored_method_data(): vo { $shopwareMethod = [ 'id' => 'shopware-method-id', - 'additionaldescription' => '', + 'additionaldescription' => $description = 'Adyen Method', 'image' => '', ]; $paymentMethod = PaymentMethod::fromRaw($rawData = [ @@ -78,7 +78,10 @@ public function it_will_enrich_a_payment_method_without_stored_method_data(): vo /** @test */ public function it_will_enrich_a_payment_method_with_stored_method_data(): void { - $shopwareMethod = ['id' => $shopwareMethodId = 'shopware-method-id']; + $shopwareMethod = [ + 'id' => $shopwareMethodId = 'shopware-method-id', + 'additionaldescription' => $description = 'Stored Method', + ]; $paymentMethod = PaymentMethod::fromRaw($rawData = [ 'id' => $storedMethodId = 'stored_method_id', 'name' => $storedMethodName = 'stored method name', @@ -87,7 +90,7 @@ public function it_will_enrich_a_payment_method_with_stored_method_data(): void 'lastFour' => $lastFour = '1234', ]); $snippetsNamespace = $this->prophesize(\Enlight_Components_Snippet_Namespace::class); - $snippetsNamespace->get($paymentMethod->adyenType()->type())->willReturn($description = 'Stored Method'); + $snippetsNamespace->get($paymentMethod->adyenType()->type())->willReturn($description); $snippetsNamespace->get('CardNumberEndingOn', $text = 'Card number ending on', true)->willReturn($text); $this->snippets->getNamespace('adyen/method/description')->willReturn($snippetsNamespace); $this->snippets->getNamespace('adyen/checkout/payment')->willReturn($snippetsNamespace); From 7d321ebb8768cc0225973c068449e22ad5b1494a Mon Sep 17 00:00:00 2001 From: davebueds Date: Tue, 3 May 2022 09:54:44 +0200 Subject: [PATCH 13/15] ASW-535 use snippets as fallback when no additional description is set --- Enricher/Payment/PaymentMethodEnricher.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Enricher/Payment/PaymentMethodEnricher.php b/Enricher/Payment/PaymentMethodEnricher.php index 130b404f..dc2b2388 100644 --- a/Enricher/Payment/PaymentMethodEnricher.php +++ b/Enricher/Payment/PaymentMethodEnricher.php @@ -39,15 +39,21 @@ public function __invoke(array $shopwareMethod, PaymentMethod $paymentMethod): a private function enrichAdditionalDescription(array $shopwareMethod, PaymentMethod $adyenMethod): string { - if (!$adyenMethod->isStoredPayment()) { - return $shopwareMethod['additionaldescription'] ?? ''; + $additionalDescription = $shopwareMethod['additionaldescription'] ?? ''; + + if ('' === $additionalDescription) { + $additionalDescription = $this->snippets + ->getNamespace('adyen/method/description') + ->get($shopwareMethod['attribute']['adyen_type'] ?? '') ?? ''; } - $description = $shopwareMethod['additionaldescription'] ?? ''; + if (!$adyenMethod->isStoredPayment()) { + return $additionalDescription; + } return sprintf( '%s%s: %s', - ($description ? $description.' ' : ''), + ($additionalDescription ? $additionalDescription.' ' : ''), $this->snippets ->getNamespace('adyen/checkout/payment') ->get('CardNumberEndingOn', 'Card number ending on', true), From 84bd9a49100311974fda2a96a7a3c6687b4af77e Mon Sep 17 00:00:00 2001 From: Damian Pastorini Date: Mon, 25 Apr 2022 07:53:20 +0200 Subject: [PATCH 14/15] ASW-493 - Config URL param --- Resources/frontend/js/jquery.adyen-payment-selection.js | 2 +- Resources/views/frontend/checkout/adyen_configuration.tpl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Resources/frontend/js/jquery.adyen-payment-selection.js b/Resources/frontend/js/jquery.adyen-payment-selection.js index d1f8190b..df54ef18 100644 --- a/Resources/frontend/js/jquery.adyen-payment-selection.js +++ b/Resources/frontend/js/jquery.adyen-payment-selection.js @@ -10,7 +10,7 @@ adyenOrderTotal: '', adyenOrderCurrency: '', resetSessionUrl: '', - adyenConfigAjaxUrl: '/frontend/adyenconfig/index', + adyenConfigAjaxUrl: '', /** * Fallback environment variable * diff --git a/Resources/views/frontend/checkout/adyen_configuration.tpl b/Resources/views/frontend/checkout/adyen_configuration.tpl index ca36ef3a..6cd0cca3 100644 --- a/Resources/views/frontend/checkout/adyen_configuration.tpl +++ b/Resources/views/frontend/checkout/adyen_configuration.tpl @@ -1,4 +1,5 @@
From 8be7bc19552e0733e5b6394ec28dcbb81cf0d9f5 Mon Sep 17 00:00:00 2001 From: Filippe Bortels Date: Thu, 5 May 2022 10:25:15 +0200 Subject: [PATCH 15/15] ASW-445 Version 3.8.0 & changelog --- plugin.xml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index 9b9b3666..4e10edab 100644 --- a/plugin.xml +++ b/plugin.xml @@ -4,7 +4,7 @@ - 3.7.0 + 3.8.0 Adyen Adyen https://adyen.com @@ -327,4 +327,18 @@ * HTML-Bereinigung beim Checkout entfernt (unter Verwendung von JSON-Antwort anstelle des HTML-Datenattributs), sodass das Design an der Zahlungsmethode angepasst werden kann. + + + * USP - Confirm modal + * Fix: Duplicate Webhook Notifications + * Fix: Mismatch of Basket Amount for guest and user accounts on payment + * Feature: Allow additional description for payment methods (with fallback to translations) + + + * USP - Confirm modal + * Fix: Duplicate Webhook Notifications + * Fix: Mismatch of Basket Amount for guest and user accounts on payment + * Feature: Allow additional description for payment methods (with fallback to translations) + +