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..e098955a 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -5,6 +5,7 @@ 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; @@ -18,20 +19,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 +30,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 +53,9 @@ public function convertNotifications(array $textNotifications): void $notification = $this->notificationBuilder->fromParams( json_decode($textNotificationItem->getTextNotification(), true) ); + + $this->notificationManager->guardDuplicate($notification); + $this->entityManager->persist($notification); } } catch (InvalidParameterException $exception) { @@ -70,10 +66,14 @@ public function convertNotifications(array $textNotifications): void $this->logger->warning( $exception->getMessage().' '.$textNotificationItem->getTextNotification() ); + } catch (DuplicateNotificationException $exception) { + $this->logger->notice( + $exception->getMessage() + ); } $this->entityManager->remove($textNotificationItem); + $this->entityManager->flush(); } - $this->entityManager->flush(); } /** diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php index fa012c17..b4ecf8f9 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; @@ -98,4 +99,22 @@ public function getLastNotificationForPspReference(string $pspReference) return; } } + + public function guardDuplicate(Notification $notification): void + { + $record = $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 ($record instanceof Notification) { + throw DuplicateNotificationException::withNotification($record); + } + } } diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index fc4274f1..7268efd9 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; @@ -26,21 +27,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 +40,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 +60,10 @@ public function processMany(Traversable $notifications): \Generator foreach ($notifications as $notification) { try { yield from $this->process($notification); + } catch (DuplicateNotificationException $exception) { + $this->logger->notice( + $exception->getMessage() + ); } catch (NoNotificationProcessorFoundException $exception) { $this->logger->notice( 'No notification processor found', @@ -105,6 +101,8 @@ public function processMany(Traversable $notifications): \Generator */ private function process(Notification $notification): \Generator { + $this->notificationManager->guardDuplicate($notification); + $processors = $this->findProcessors($notification); if (empty($processors)) { 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/Enricher/Payment/PaymentMethodEnricher.php b/Enricher/Payment/PaymentMethodEnricher.php index 21b67139..dc2b2388 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,19 +37,23 @@ 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()) ?? ''; + $additionalDescription = $shopwareMethod['additionaldescription'] ?? ''; + + if ('' === $additionalDescription) { + $additionalDescription = $this->snippets + ->getNamespace('adyen/method/description') + ->get($shopwareMethod['attribute']['adyen_type'] ?? '') ?? ''; + } if (!$adyenMethod->isStoredPayment()) { - return $description; + return $additionalDescription; } return sprintf( '%s%s: %s', - ($description ? $description.' ' : ''), + ($additionalDescription ? $additionalDescription.' ' : ''), $this->snippets ->getNamespace('adyen/checkout/payment') ->get('CardNumberEndingOn', 'Card number ending on', true), diff --git a/Exceptions/DuplicateNotificationException.php b/Exceptions/DuplicateNotificationException.php new file mode 100644 index 00000000..8edf5474 --- /dev/null +++ b/Exceptions/DuplicateNotificationException.php @@ -0,0 +1,28 @@ +getId(), + $notification->getOrderId(), + $notification->getPspReference(), + $notification->getStatus(), + $notification->getPaymentMethod(), + $notification->getEventCode(), + $notification->isSuccess(), + $notification->getMerchantAccountCode(), + $notification->getAmountValue(), + $notification->getAmountCurrency() + )); + } +} diff --git a/Resources/frontend/js/jquery.adyen-disable-payment.js b/Resources/frontend/js/jquery.adyen-disable-payment.js index 8bdf54a8..b815f5f1 100644 --- a/Resources/frontend/js/jquery.adyen-disable-payment.js +++ b/Resources/frontend/js/jquery.adyen-disable-payment.js @@ -23,15 +23,36 @@ * 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 */ - 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; me.applyDataAttributes(); + me.modalContent = $(me.opts.modalSelector).html() || ''; me.$el.on('click', $.proxy(me.enableDisableButtonClick, me)); }, enableDisableButtonClick: function () { @@ -39,7 +60,30 @@ if (0 === me.opts.adyenStoredMethodId.length) { return; } + if('' === me.modalContent){ + return; + } + me.modal = $.modal.open(me.modalContent, { + showCloseButton: true, + 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; $.loadingIndicator.open(); + $.loadingIndicator.loader.$loader.addClass('over-modal'); $.post({ url: me.opts.adyenDisableTokenUrl, dataType: 'json', @@ -58,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/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/frontend/less/all.less b/Resources/frontend/less/all.less index 6df53d6b..cab1f7fd 100644 --- a/Resources/frontend/less/all.less +++ b/Resources/frontend/less/all.less @@ -27,6 +27,25 @@ .adyen-modal .content { padding: 20px; + + .modal-error-container { + margin-top: 2em; + } +} + +.over-modal { + z-index: 7000; +} + +.disable-token-confirmation { + height: auto; + max-height: 18em; + + .buttons-container { + display: flex; + justify-content: space-evenly; + margin-top: 2em; + } } .alert.is--adyen-error { 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 @@