From 09dfcf32efab159f84264f6a7574376c9a1ee5e9 Mon Sep 17 00:00:00 2001
From: Can Demiralp
Date: Tue, 24 Dec 2024 10:48:18 +0100
Subject: [PATCH] [ECP-9240] Refactor the process to download the Apple Pay
domain association file, and change the misleading UI field label (#2818)
* [ECP-9240] Refactor download process
* [ECP-9240] Write unit tests for the controller
* [ECP-9240] Formatting
* [ECP-9240] Write unit tests
* [ECP-9240] Convert string label to phrase for localisation
* [ECP-9240] Start using CDN to download domain association file
---------
Co-authored-by: Can Demiralp
---
.../DownloadApplePayCertificate.php | 158 ------------------
.../DownloadApplePayDomainAssociationFile.php | 82 +++++++++
...> ApplePayDomainAssociationFileButton.php} | 14 +-
...nloadApplePayDomainAssociationFileTest.php | 144 ++++++++++++++++
...pplePayDomainAssociationFileButtonTest.php | 102 +++++++++++
.../adyen_alternative_payment_methods.xml | 4 +-
.../templates/config/applepay_button.phtml | 3 -
...lepay_domain_association_file_button.phtml | 4 +
8 files changed, 341 insertions(+), 170 deletions(-)
delete mode 100644 Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php
create mode 100644 Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php
rename Model/Config/Adminhtml/{ApplepayCertificateButton.php => ApplePayDomainAssociationFileButton.php} (76%)
create mode 100644 Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php
create mode 100644 Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php
delete mode 100644 view/adminhtml/templates/config/applepay_button.phtml
create mode 100644 view/adminhtml/templates/config/applepay_domain_association_file_button.phtml
diff --git a/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php b/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php
deleted file mode 100644
index 5af0c4dcea..0000000000
--- a/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php
+++ /dev/null
@@ -1,158 +0,0 @@
-
- */
-
-namespace Adyen\Payment\Controller\Adminhtml\Configuration;
-
-use Adyen\AdyenException;
-use Adyen\Payment\Helper\Config;
-use Adyen\Payment\Logger\AdyenLogger;
-use Magento\Framework\App\ResponseInterface;
-use Magento\Framework\Controller\Result\Redirect;
-use Magento\Framework\Controller\ResultInterface;
-use Magento\Framework\Exception\FileSystemException;
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Filesystem\DirectoryList;
-use Magento\Backend\App\Action\Context;
-use Magento\Framework\Controller\ResultFactory;
-use Magento\Backend\App\Action;
-use Magento\Framework\Filesystem\Io\File;
-use Exception;
-use ZipArchive;
-
-class DownloadApplePayCertificate extends Action
-{
- const READ_LENGTH = 2048;
- const MAX_FILES = 10;
- const MAX_SIZE = 1000000;
- const MAX_RATIO = 5;
- const FILE_NAME = 'apple-developer-merchantid-domain-association';
- const APPLEPAY_CERTIFICATE_URL = 'https://docs.adyen.com/payment-methods/apple-pay/web-component/apple-developer-merchantid-domain-association-2024.zip';
- private $directoryList;
- private $fileIo;
- private $adyenLogger;
-
- public function __construct(
- Context $context,
- DirectoryList $directoryList,
- File $fileIo,
- AdyenLogger $adyenLogger
- )
- {
- parent::__construct($context);
- $this->directoryList = $directoryList;
- $this->fileIo = $fileIo;
- $this->adyenLogger = $adyenLogger;
- }
-
- /**
- * @return ResponseInterface|Redirect|Redirect&ResultInterface|ResultInterface
- * @throws FileSystemException
- * @throws LocalizedExceptionff
- */
- public function execute()
- {
- $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
- $redirect->setUrl($this->_redirect->getRefererUrl());
-
- $pubPath = $this->directoryList->getPath('pub');
- $directoryName = '.well-known';
-
- $wellknownPath = $pubPath . '/' . $directoryName;
- $applepayPath = $wellknownPath . '/' . self::FILE_NAME;
-
- $applepayUrl = self::APPLEPAY_CERTIFICATE_URL;
-
- try {
- if ($this->fileIo->checkAndCreateFolder($wellknownPath, 0700)) {
- $this->downloadAndUnzip($applepayUrl, $wellknownPath);
- } else {
- $this->fileIo->chmod($wellknownPath, 0770);
- if (!$this->fileIo->fileExists($applepayPath)) {
- $this->downloadAndUnzip($applepayUrl, $wellknownPath);
- }
- }
- } catch (Exception $e) {
- $errormessage = 'Failed to download the ApplePay certificate, please do so manually';
- $this->adyenLogger->addAdyenWarning($errormessage);
- $this->messageManager->addErrorMessage($errormessage);
- }
-
- return $redirect;
- }
-
- /**
- * @param string $applepayUrl
- * @param string $applepayPath
- * @return void
- * @throws LocalizedException
- */
- private function downloadAndUnzip(string $applepayUrl, string $applepayPath)
- {
- $tmpPath = tempnam(sys_get_temp_dir(), self::FILE_NAME);
- file_put_contents($tmpPath, file_get_contents($applepayUrl));
-
- $zip = new ZipArchive;
- $fileCount = 0;
- $totalSize = 0;
-
- if ($zip->open($tmpPath) === true) {
- for ($i = 0; $i < $zip->numFiles; $i++) {
- $filename = $zip->getNameIndex($i);
- if (self::FILE_NAME !== $filename) {
- continue;
- }
- $stats = $zip->statIndex($i);
-
- // Prevent ZipSlip path traversal (S6096)
- if (strpos($filename, '../') !== false ||
- substr($filename, 0, 1) === '/') {
- throw new AdyenException('The zip file is trying to ZipSlip please check the file');
- }
-
- if (substr($filename, -1) !== '/') {
- $fileCount++;
- if ($fileCount > 10) {
- // Reached max. number of files
- throw new AdyenException('Reached max number of files please check the zip file');
- }
-
- $applepayCerticateFilestream = $zip->getStream($filename); // Compliant
- $currentSize = 0;
- while (!feof($applepayCerticateFilestream)) {
- $currentSize += self::READ_LENGTH;
- $totalSize += self::READ_LENGTH;
-
- if ($totalSize > self::MAX_SIZE) {
- // Reached max. size
- throw new AdyenException('The file is larger than expected please check the zip file');
- }
-
- // Additional protection: check compression ratio
- if ($stats['comp_size'] > 0) {
- $ratio = $currentSize / $stats['comp_size'];
- if ($ratio > self::MAX_RATIO) {
- // Reached max. compression ratio
- throw new AdyenException('The uncompressed file is larger than expected');
- }
- }
- file_put_contents(
- $applepayPath .'/' . $filename,
- fread($applepayCerticateFilestream, $totalSize),
- FILE_APPEND
- );
- }
- fclose($applepayCerticateFilestream);
- }
- }
- $zip->close();
- }
- }
-}
diff --git a/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php b/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php
new file mode 100644
index 0000000000..0726b316cc
--- /dev/null
+++ b/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php
@@ -0,0 +1,82 @@
+
+ */
+
+namespace Adyen\Payment\Controller\Adminhtml\Configuration;
+
+use Adyen\Payment\Logger\AdyenLogger;
+use Exception;
+use Magento\Framework\App\ResponseInterface;
+use Magento\Framework\Controller\ResultInterface;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Filesystem\DirectoryList;
+use Magento\Backend\App\Action\Context;
+use Magento\Framework\Controller\ResultFactory;
+use Magento\Backend\App\Action;
+use Magento\Framework\Filesystem\Io\File;
+
+class DownloadApplePayDomainAssociationFile extends Action
+{
+ const FILE_NAME = 'apple-developer-merchantid-domain-association';
+ const REMOTE_PATH = 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known';
+ const PUB_PATH = 'pub';
+ const WELL_KNOWN_PATH = '.well-known';
+
+ public function __construct(
+ private readonly Context $context,
+ private readonly DirectoryList $directoryList,
+ private readonly File $fileIo,
+ private readonly AdyenLogger $adyenLogger
+ ) {
+ parent::__construct($context);
+ }
+
+ /**
+ * @return ResultInterface|ResponseInterface
+ * @throws FileSystemException
+ */
+ public function execute(): ResultInterface|ResponseInterface
+ {
+ $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
+ $redirect->setUrl($this->_redirect->getRefererUrl());
+
+ try {
+ $pubPath = $this->directoryList->getPath(self::PUB_PATH);
+
+ $this->fileIo->checkAndCreateFolder(
+ sprintf("%s/%s", $pubPath, self::WELL_KNOWN_PATH),
+ 0700
+ );
+
+ $source = sprintf("%s/%s", self::REMOTE_PATH, self::FILE_NAME);
+ $destination = sprintf("%s/%s/%s", $pubPath, self::WELL_KNOWN_PATH, self::FILE_NAME);
+
+ $file = $this->fileIo->read($source, $destination);
+
+ if (!$file) {
+ $errorMessage =
+ __('Error while downloading Apple Pay domain association file from the remote source!');
+ $this->adyenLogger->error(sprintf("%s %s", $errorMessage, $source));
+ $this->messageManager->addErrorMessage($errorMessage);
+ } else {
+ $successMessage = __('Apple Pay domain association file has been downloaded successfully!');
+ $this->adyenLogger->addAdyenDebug($successMessage);
+ $this->messageManager->addSuccessMessage($successMessage);
+ }
+ } catch (Exception $e) {
+ $errorMessage =
+ __('Unknown error while downloading Apple Pay domain association file!');
+ $this->adyenLogger->error(sprintf("%s %s", $errorMessage, $e->getMessage()));
+ $this->messageManager->addErrorMessage($errorMessage);
+ }
+
+ return $redirect;
+ }
+}
diff --git a/Model/Config/Adminhtml/ApplepayCertificateButton.php b/Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php
similarity index 76%
rename from Model/Config/Adminhtml/ApplepayCertificateButton.php
rename to Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php
index 4dc52c5ded..16de30dd46 100644
--- a/Model/Config/Adminhtml/ApplepayCertificateButton.php
+++ b/Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php
@@ -16,14 +16,14 @@
use Magento\Backend\Helper\Data;
use Magento\Framework\Data\Form\Element\AbstractElement;
-class ApplepayCertificateButton extends Field
+class ApplePayDomainAssociationFileButton extends Field
{
- const APPLEPAY_BUTTON = 'Adyen_Payment::config/applepay_button.phtml';
+ const APPLEPAY_BUTTON = 'Adyen_Payment::config/applepay_domain_association_file_button.phtml';
/**
* @var Data
*/
- protected $backendHelper;
+ protected Data $backendHelper;
/**
* @param Context $context
@@ -40,9 +40,9 @@ public function __construct(
}
/**
- * @return $this|ApplepayCertificateButton
+ * @return $this|ApplePayDomainAssociationFileButton
*/
- protected function _prepareLayout()
+ protected function _prepareLayout(): ApplePayDomainAssociationFileButton|static
{
parent::_prepareLayout();
@@ -57,7 +57,7 @@ protected function _prepareLayout()
* @param AbstractElement $element
* @return string
*/
- protected function _getElementHtml(AbstractElement $element)
+ protected function _getElementHtml(AbstractElement $element): string
{
$this->addData([
'id' => 'addbutton_button',
@@ -73,6 +73,6 @@ protected function _getElementHtml(AbstractElement $element)
*/
public function getActionUrl(): string
{
- return $this->backendHelper->getUrl("adyen/configuration/DownloadApplePayCertificate");
+ return $this->backendHelper->getUrl("adyen/configuration/DownloadApplePayDomainAssociationFile");
}
}
diff --git a/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php b/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php
new file mode 100644
index 0000000000..954e61f634
--- /dev/null
+++ b/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php
@@ -0,0 +1,144 @@
+redirectMock = $this->createMock(RedirectInterface::class);
+
+ $this->resultMock = $this->createGeneratedMock(
+ Redirect::class
+ );
+
+ $this->resultFactoryMock = $this->createGeneratedMock(
+ ResultFactory::class,
+ ['create']
+ );
+ $this->resultFactoryMock->method('create')->willReturn($this->resultMock);
+
+ $this->managerMock = $this->createMock(ManagerInterface::class);
+
+ $this->contextMock = $this->createMock(Context::class);
+ $this->contextMock->method('getResultFactory')->willReturn($this->resultFactoryMock);
+ $this->contextMock->method('getRedirect')->willReturn($this->redirectMock);
+ $this->contextMock->method('getMessageManager')->willReturn($this->managerMock);
+
+ $this->directoryListMock = $this->createMock(DirectoryList::class);
+ $this->fileIoMock = $this->createMock(File::class);
+ $this->adyenLoggerMock = $this->createMock(AdyenLogger::class);
+
+
+ $this->downloadApplePayDomainAssociationFile = new DownloadApplePayDomainAssociationFile(
+ $this->contextMock,
+ $this->directoryListMock,
+ $this->fileIoMock,
+ $this->adyenLoggerMock
+ );
+ }
+
+ /**
+ * @return void
+ * @throws FileSystemException
+ */
+ public function testDownloadFileSuccessfully(): void
+ {
+ $this->directoryListMock->method('getPath')
+ ->with('pub')
+ ->willReturn('/var/www/html/pub');
+
+ $this->fileIoMock->method('checkAndCreateFolder')
+ ->with('/var/www/html/pub/.well-known')
+ ->willReturn(true);
+
+ $this->fileIoMock->method('read')
+ ->with(
+ 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known/apple-developer-merchantid-domain-association',
+ '/var/www/html/pub/.well-known/apple-developer-merchantid-domain-association'
+ )
+ ->willReturn(true);
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('addAdyenDebug');
+ $this->managerMock->expects($this->once())->method('addSuccessMessage');
+
+ $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute();
+
+ $this->assertInstanceOf(Redirect::class, $redirectResponse);
+ }
+
+ /**
+ * @return void
+ * @throws FileSystemException
+ */
+ public function testHttpNotFound(): void
+ {
+ $this->directoryListMock->method('getPath')
+ ->with('pub')
+ ->willReturn('/var/www/html/pub');
+
+ $this->fileIoMock->method('checkAndCreateFolder')
+ ->with('/var/www/html/pub/.well-known')
+ ->willReturn(true);
+
+ $this->fileIoMock->method('read')
+ ->with(
+ 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known/apple-developer-merchantid-domain-association',
+ '/var/www/html/pub/.well-known/apple-developer-merchantid-domain-association'
+ )
+ ->willReturn(false);
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+ $this->managerMock->expects($this->once())->method('addErrorMessage');
+
+ $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute();
+
+ $this->assertInstanceOf(Redirect::class, $redirectResponse);
+ }
+
+ /**
+ * @return void
+ * @throws FileSystemException
+ */
+ public function testFileSystemIOError(): void
+ {
+ $this->directoryListMock->method('getPath')
+ ->with('pub')
+ ->willReturn('/var/www/html/pub');
+
+ $this->fileIoMock->method('checkAndCreateFolder')
+ ->willThrowException(new LocalizedException(__('mock error message')));
+
+ $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error');
+ $this->managerMock->expects($this->once())->method('addErrorMessage');
+
+ $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute();
+
+ $this->assertInstanceOf(Redirect::class, $redirectResponse);
+ }
+}
diff --git a/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php b/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php
new file mode 100644
index 0000000000..f79be89c48
--- /dev/null
+++ b/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php
@@ -0,0 +1,102 @@
+createMock(ObjectManagerInterface::class);
+ ObjectManager::setInstance($objectManagerMock);
+
+ $this->managerMock = $this->createMock(ManagerInterface::class);
+ $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class);
+
+ $this->contextMock = $this->createMock(Context::class);
+ $this->contextMock->method('getScopeConfig')->willReturn($this->scopeConfigMock);
+ $this->contextMock->method('getEventManager')->willReturn($this->managerMock);
+
+ $this->backendHelperMock = $this->createMock(Data::class);
+ $this->layoutMock = $this->createMock(LayoutInterface::class);
+
+ $this->applePayDomainAssociationFileButton = new ApplePayDomainAssociationFileButton(
+ $this->contextMock,
+ $this->backendHelperMock,
+ []
+ );
+ }
+
+ /**
+ * @return void
+ */
+ public function tearDown(): void
+ {
+ $this->applePayDomainAssociationFileButton = null;
+ }
+
+ /**
+ * Asserts default HTML template value
+ *
+ * @return void
+ */
+ public function testGetElementHtml()
+ {
+ $expected = ' | | |
';
+
+ $result = $this->applePayDomainAssociationFileButton
+ ->render($this->createMock(AbstractElement::class));
+
+ $this->assertEquals($expected, $result);
+ }
+
+ /**
+ * Asserts return type of the button's backend model
+ *
+ * @return void
+ */
+ public function testPrepareLayout()
+ {
+ $result = $this->applePayDomainAssociationFileButton->setLayout($this->layoutMock);
+ $this->assertInstanceOf(ApplePayDomainAssociationFileButton::class, $result);
+ }
+
+ /**
+ * Asserts file download controller URL
+ *
+ * @return void
+ */
+ public function testGetActionUrl()
+ {
+ $expected = 'https://www.magento.demo/adyen/configuration/DownloadApplePayDomainAssociationFile';
+ $this->backendHelperMock->expects($this->once())
+ ->method('getUrl')
+ ->with('adyen/configuration/DownloadApplePayDomainAssociationFile', [])
+ ->willReturn($expected);
+
+ $url = $this->applePayDomainAssociationFileButton->getActionUrl();
+
+ $this->assertEquals($expected, $url);
+ }
+}
diff --git a/etc/adminhtml/system/adyen_alternative_payment_methods.xml b/etc/adminhtml/system/adyen_alternative_payment_methods.xml
index 21dcfb7a81..ae858674c6 100755
--- a/etc/adminhtml/system/adyen_alternative_payment_methods.xml
+++ b/etc/adminhtml/system/adyen_alternative_payment_methods.xml
@@ -19,8 +19,8 @@
Set up additional payment methods to accept online and in-app payments and eliminate the need for traditional card-based transactions.
]]>
-
- Adyen\Payment\Model\Config\Adminhtml\ApplepayCertificateButton
+
+ Adyen\Payment\Model\Config\Adminhtml\ApplePayDomainAssociationFileButton
diff --git a/view/adminhtml/templates/config/applepay_button.phtml b/view/adminhtml/templates/config/applepay_button.phtml
deleted file mode 100644
index e57195c9fe..0000000000
--- a/view/adminhtml/templates/config/applepay_button.phtml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml b/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml
new file mode 100644
index 0000000000..a25f333024
--- /dev/null
+++ b/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml
@@ -0,0 +1,4 @@
+
+