diff --git a/Api/Repository/AdyenNotificationRepositoryInterface.php b/Api/Repository/AdyenNotificationRepositoryInterface.php
new file mode 100644
index 0000000000..67453ec80b
--- /dev/null
+++ b/Api/Repository/AdyenNotificationRepositoryInterface.php
@@ -0,0 +1,39 @@
+
+ */
+
+namespace Adyen\Payment\Api\Repository;
+
+use Adyen\Payment\Api\Data\NotificationInterface;
+use Magento\Framework\Api\SearchCriteriaInterface;
+use Magento\Framework\Api\SearchResultsInterface;
+use Magento\Framework\Exception\LocalizedException;
+
+interface AdyenNotificationRepositoryInterface
+{
+ /**
+ * Retrieve Adyen Notification entities which match a specified criteria.
+ *
+ * @param SearchCriteriaInterface $searchCriteria
+ * @return SearchResultsInterface
+ *
+ * @throws LocalizedException
+ */
+ public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface;
+
+ /**
+ * Deletes a specified Adyen notification.
+ *
+ * @param NotificationInterface $entity The notification ID.
+ * @return bool
+ */
+ public function delete(NotificationInterface $entity): bool;
+}
diff --git a/Cron/CleanupNotifications.php b/Cron/CleanupNotifications.php
new file mode 100644
index 0000000000..fc3bf26dfc
--- /dev/null
+++ b/Cron/CleanupNotifications.php
@@ -0,0 +1,66 @@
+
+ */
+
+namespace Adyen\Payment\Cron;
+
+use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface;
+use Adyen\Payment\Cron\Providers\NotificationsProviderInterface;
+use Adyen\Payment\Helper\Config;
+use Adyen\Payment\Logger\AdyenLogger;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Store\Model\StoreManagerInterface;
+
+class CleanupNotifications
+{
+ /**
+ * @param NotificationsProviderInterface[] $providers
+ */
+ public function __construct(
+ private readonly array $providers,
+ private readonly AdyenLogger $adyenLogger,
+ private readonly Config $configHelper,
+ private readonly StoreManagerInterface $storeManager,
+ private readonly AdyenNotificationRepositoryInterface $adyenNotificationRepository
+ ) { }
+
+ /**
+ * @return void
+ * @throws NoSuchEntityException
+ */
+ public function execute(): void
+ {
+ $storeId = $this->storeManager->getStore()->getId();
+ $isWebhookCleanupEnabled = $this->configHelper->getIsWebhookCleanupEnabled($storeId);
+
+ if ($isWebhookCleanupEnabled) {
+ $numberOfItemsRemoved = 0;
+
+ foreach ($this->providers as $provider) {
+ foreach ($provider->provide() as $notificationToCleanup) {
+ $isSuccessfullyDeleted = $this->adyenNotificationRepository->delete($notificationToCleanup);
+
+ if ($isSuccessfullyDeleted) {
+ $numberOfItemsRemoved++;
+ }
+ }
+ }
+
+ $successMessage = sprintf(
+ __('%s webhook notifications have been cleaned-up by the CleanupNotifications job.'),
+ $numberOfItemsRemoved
+ );
+ $this->adyenLogger->addAdyenDebug($successMessage);
+ } else {
+ $message = __('Webhook notification clean-up feature is disabled. The job has been skipped!');
+ $this->adyenLogger->addAdyenDebug($message);
+ }
+ }
+}
diff --git a/Cron/Providers/NotificationsProviderInterface.php b/Cron/Providers/NotificationsProviderInterface.php
new file mode 100644
index 0000000000..34f9b06256
--- /dev/null
+++ b/Cron/Providers/NotificationsProviderInterface.php
@@ -0,0 +1,25 @@
+
+ */
+
+namespace Adyen\Payment\Cron\Providers;
+
+interface NotificationsProviderInterface
+{
+ /**
+ * @return array
+ */
+ public function provide(): array;
+
+ /**
+ * @return string
+ */
+ public function getProviderName(): string;
+}
diff --git a/Cron/Providers/ProcessedOldNotificationsProvider.php b/Cron/Providers/ProcessedOldNotificationsProvider.php
new file mode 100644
index 0000000000..60196b7c12
--- /dev/null
+++ b/Cron/Providers/ProcessedOldNotificationsProvider.php
@@ -0,0 +1,63 @@
+
+ */
+
+namespace Adyen\Payment\Cron\Providers;
+
+use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface;
+use Adyen\Payment\Helper\Config;
+use Adyen\Payment\Logger\AdyenLogger;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Store\Model\StoreManagerInterface;
+
+class ProcessedOldNotificationsProvider implements NotificationsProviderInterface
+{
+ public function __construct(
+ private readonly AdyenNotificationRepositoryInterface $adyenNotificationRepository,
+ private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
+ private readonly Config $configHelper,
+ private readonly StoreManagerInterface $storeManager,
+ private readonly AdyenLogger $adyenLogger
+ ) { }
+
+ public function provide(): array
+ {
+ $storeId = $this->storeManager->getStore()->getId();
+ $numberOfDays = $this->configHelper->getRequiredDaysForOldWebhooks($storeId);
+
+ $dateFrom = date('Y-m-d H:i:s', time() - $numberOfDays * 24 * 60 * 60);
+
+ $searchCriteria = $this->searchCriteriaBuilder
+ ->addFilter('done', 1)
+ ->addFilter('processing', 0)
+ ->addFilter('created_at', $dateFrom, 'lteq')
+ ->create();
+
+ try {
+ $items = $this->adyenNotificationRepository->getList($searchCriteria);
+ return $items->getItems();
+ } catch (LocalizedException $e) {
+ $errorMessage = sprintf(
+ __('An error occurred while providing notifications older than %s days!'),
+ $numberOfDays
+ );
+
+ $this->adyenLogger->error($errorMessage);
+
+ return [];
+ }
+ }
+
+ public function getProviderName(): string
+ {
+ return "Adyen processed old webhook notifications";
+ }
+}
diff --git a/Helper/Config.php b/Helper/Config.php
index 8657bfe26a..483960e026 100644
--- a/Helper/Config.php
+++ b/Helper/Config.php
@@ -57,6 +57,8 @@ class Config
const XML_RECURRING_CONFIGURATION = 'recurring_configuration';
const XML_ALLOW_MULTISTORE_TOKENS = 'allow_multistore_tokens';
const XML_THREEDS_FLOW = 'threeds_flow';
+ const XML_CLEANUP_OLD_WEBHOOKS = 'cleanup_old_webhooks';
+ const XML_REQUIRED_DAYS_OLD_WEBHOOKS = 'required_days_old_webhooks';
protected ScopeConfigInterface $scopeConfig;
private EncryptorInterface $encryptor;
@@ -592,6 +594,25 @@ public function getThreeDSFlow(int $storeId = null): string
);
}
+ public function getIsWebhookCleanupEnabled(int $storeId = null): bool
+ {
+ return $this->getConfigData(
+ self::XML_CLEANUP_OLD_WEBHOOKS,
+ self::XML_ADYEN_ABSTRACT_PREFIX,
+ $storeId,
+ true
+ );
+ }
+
+ public function getRequiredDaysForOldWebhooks(int $storeId = null): int
+ {
+ return (int) $this->getConfigData(
+ self::XML_REQUIRED_DAYS_OLD_WEBHOOKS,
+ self::XML_ADYEN_ABSTRACT_PREFIX,
+ $storeId
+ );
+ }
+
public function getConfigData(string $field, string $xmlPrefix, ?int $storeId, bool $flag = false): mixed
{
$path = implode("/", [self::XML_PAYMENT_PREFIX, $xmlPrefix, $field]);
diff --git a/Model/AdyenNotificationRepository.php b/Model/AdyenNotificationRepository.php
new file mode 100644
index 0000000000..277d12be60
--- /dev/null
+++ b/Model/AdyenNotificationRepository.php
@@ -0,0 +1,62 @@
+
+ */
+
+namespace Adyen\Payment\Model;
+
+use Adyen\Payment\Api\Data\NotificationInterface;
+use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface;
+use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory;
+use Magento\Framework\Api\Search\SearchResultFactory;
+use Magento\Framework\Api\SearchCriteria\CollectionProcessor;
+use Magento\Framework\Api\SearchCriteriaInterface;
+use Magento\Framework\Api\SearchResultsInterface;
+use Magento\Framework\ObjectManagerInterface;
+
+class AdyenNotificationRepository implements AdyenNotificationRepositoryInterface
+{
+ /**
+ * @param SearchResultFactory $searchResultsFactory
+ * @param CollectionFactory $collectionFactory
+ * @param CollectionProcessor $collectionProcessor
+ * @param ObjectManagerInterface $objectManager
+ * @param string $resourceModel
+ */
+ public function __construct(
+ private readonly SearchResultFactory $searchResultsFactory,
+ private readonly CollectionFactory $collectionFactory,
+ private readonly CollectionProcessor $collectionProcessor,
+ private readonly ObjectManagerInterface $objectManager,
+ private readonly string $resourceModel
+ ) { }
+
+ /**
+ * @param SearchCriteriaInterface $searchCriteria
+ * @return SearchResultsInterface
+ */
+ public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
+ {
+ $searchResult = $this->searchResultsFactory->create();
+ $collection = $this->collectionFactory->create();
+ $this->collectionProcessor->process($searchCriteria, $collection);
+ $searchResult->setItems($collection->getItems());
+ $searchResult->setTotalCount($collection->getSize());
+
+ return $searchResult;
+ }
+
+ public function delete(NotificationInterface $entity): bool
+ {
+ $resource = $this->objectManager->get($this->resourceModel);
+ $resource->delete($entity);
+
+ return true;
+ }
+}
diff --git a/etc/adminhtml/system/adyen_testing_performance.xml b/etc/adminhtml/system/adyen_testing_performance.xml
index c384428841..358be8ffcf 100644
--- a/etc/adminhtml/system/adyen_testing_performance.xml
+++ b/etc/adminhtml/system/adyen_testing_performance.xml
@@ -55,5 +55,14 @@
]]>
+
+
+ Magento\Config\Model\Config\Source\Yesno
+ payment/adyen_abstract/cleanup_old_webhooks
+
+ Webhooks older than certain days will be removed from the database by a cronjob if this feature is enabled.
+ The default value is 90 days and this can be configured by overriding `payment/adyen_abstract/required_days_old_webhooks` configuration path.
+
+
diff --git a/etc/config.xml b/etc/config.xml
index b432bd83ff..76e66ba2b4 100755
--- a/etc/config.xml
+++ b/etc/config.xml
@@ -34,6 +34,8 @@
canceled
manual
1
+ 0
+ 90
0
diff --git a/etc/crontab.xml b/etc/crontab.xml
index d638f45ad1..cf093da854 100755
--- a/etc/crontab.xml
+++ b/etc/crontab.xml
@@ -24,5 +24,8 @@
0 0 * * *
+
+ 0 0 * * *
+
diff --git a/etc/di.xml b/etc/di.xml
index aefb9b552a..7a3438e34c 100755
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -1693,6 +1693,8 @@
+
@@ -1717,6 +1719,20 @@
+
+
+ Adyen\Payment\Model\ResourceModel\Notification
+
+
+
+
+
+ -
+ Adyen\Payment\Cron\Providers\ProcessedOldNotificationsProvider
+
+
+
+