diff --git a/src/Components/ApplePayDirect/ApplePayDirect.php b/src/Components/ApplePayDirect/ApplePayDirect.php
index 013768326..f02204b59 100644
--- a/src/Components/ApplePayDirect/ApplePayDirect.php
+++ b/src/Components/ApplePayDirect/ApplePayDirect.php
@@ -2,10 +2,14 @@
namespace Kiener\MolliePayments\Components\ApplePayDirect;
+use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayValidationUrlAllowListCanNotBeEmptyException;
+use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayValidationUrlNotInAllowListException;
+use Kiener\MolliePayments\Components\ApplePayDirect\Gateways\ApplePayValidationUrlAllowListGateway;
use Kiener\MolliePayments\Components\ApplePayDirect\Models\ApplePayCart;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayDomainVerificationService;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayFormatter;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayShippingBuilder;
+use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayValidationUrlSanitizer;
use Kiener\MolliePayments\Facade\MolliePaymentDoPay;
use Kiener\MolliePayments\Factory\MollieApiFactory;
use Kiener\MolliePayments\Handler\Method\ApplePayPayment;
@@ -105,6 +109,16 @@ class ApplePayDirect
*/
private $repoOrderAdresses;
+ /**
+ * @var ApplePayValidationUrlAllowListGateway
+ */
+ private $validationUrlAllowListGateway;
+
+ /**
+ * @var ApplePayValidationUrlSanitizer
+ */
+ private $validationUrlSanitizer;
+
/**
* @param ApplePayDomainVerificationService $domainFileDownloader
@@ -121,8 +135,10 @@ class ApplePayDirect
* @param ShopService $shopService
* @param OrderService $orderService
* @param OrderAddressRepositoryInterface $repoOrderAdresses
+ * @param ApplePayValidationUrlAllowListGateway $validationUrlAllowListGateway
+ * @param ApplePayValidationUrlSanitizer $validationUrlSanitizer
*/
- public function __construct(ApplePayDomainVerificationService $domainFileDownloader, ApplePayPayment $paymentHandler, MolliePaymentDoPay $molliePayments, CartServiceInterface $cartService, ApplePayFormatter $formatter, ApplePayShippingBuilder $shippingBuilder, SettingsService $pluginSettings, CustomerService $customerService, PaymentMethodRepository $repoPaymentMethods, CartBackupService $cartBackupService, MollieApiFactory $mollieApiFactory, ShopService $shopService, OrderService $orderService, OrderAddressRepositoryInterface $repoOrderAdresses)
+ public function __construct(ApplePayDomainVerificationService $domainFileDownloader, ApplePayPayment $paymentHandler, MolliePaymentDoPay $molliePayments, CartServiceInterface $cartService, ApplePayFormatter $formatter, ApplePayShippingBuilder $shippingBuilder, SettingsService $pluginSettings, CustomerService $customerService, PaymentMethodRepository $repoPaymentMethods, CartBackupService $cartBackupService, MollieApiFactory $mollieApiFactory, ShopService $shopService, OrderService $orderService, OrderAddressRepositoryInterface $repoOrderAdresses, ApplePayValidationUrlAllowListGateway $validationUrlAllowListGateway, ApplePayValidationUrlSanitizer $validationUrlSanitizer)
{
$this->domainFileDownloader = $domainFileDownloader;
$this->paymentHandler = $paymentHandler;
@@ -138,6 +154,8 @@ public function __construct(ApplePayDomainVerificationService $domainFileDownloa
$this->shopService = $shopService;
$this->orderService = $orderService;
$this->repoOrderAdresses = $repoOrderAdresses;
+ $this->validationUrlAllowListGateway = $validationUrlAllowListGateway;
+ $this->validationUrlSanitizer = $validationUrlSanitizer;
}
@@ -473,6 +491,29 @@ public function createPayment(OrderEntity $order, string $shopwareReturnUrl, str
return $paymentData->getMollieID();
}
+ /**
+ * @param string $validationUrl
+ * @throws ApplePayValidationUrlAllowListCanNotBeEmptyException
+ * @throws ApplePayValidationUrlNotInAllowListException
+ * @return string
+ */
+ public function validateValidationUrl(string $validationUrl): string
+ {
+ $allowList = $this->validationUrlAllowListGateway->getAllowList();
+
+ if ($allowList->isEmpty()) {
+ throw new ApplePayValidationUrlAllowListCanNotBeEmptyException();
+ }
+
+ $validationUrl = $this->validationUrlSanitizer->sanitizeValidationUrl($validationUrl);
+
+ if ($allowList->contains($validationUrl) === false) {
+ throw new ApplePayValidationUrlNotInAllowListException($validationUrl);
+ }
+
+ return $validationUrl;
+ }
+
/**
* @param Cart $cart
* @return ApplePayCart
diff --git a/src/Components/ApplePayDirect/Exceptions/ApplePayValidationUrlAllowListCanNotBeEmptyException.php b/src/Components/ApplePayDirect/Exceptions/ApplePayValidationUrlAllowListCanNotBeEmptyException.php
new file mode 100644
index 000000000..c7a7ed58b
--- /dev/null
+++ b/src/Components/ApplePayDirect/Exceptions/ApplePayValidationUrlAllowListCanNotBeEmptyException.php
@@ -0,0 +1,12 @@
+systemConfigService = $systemConfigService;
+ }
+
+ /**
+ * Get the ApplePayValidationUrlAllowList
+ *
+ * @return ApplePayValidationUrlAllowList
+ */
+ public function getAllowList(): ApplePayValidationUrlAllowList
+ {
+ $allowList = $this->systemConfigService->get('MolliePayments.config.ApplePayValidationAllowList');
+
+ if (is_string($allowList) === false || empty($allowList)) {
+ return ApplePayValidationUrlAllowList::create();
+ }
+
+ $allowList = trim($allowList);
+
+ $items = explode(',', $allowList);
+ $items = array_map([ApplePayValidationUrlAllowListItem::class, 'create'], $items);
+
+ return ApplePayValidationUrlAllowList::create(...$items);
+ }
+}
diff --git a/src/Components/ApplePayDirect/Models/ApplePayValidationUrlAllowListItem.php b/src/Components/ApplePayDirect/Models/ApplePayValidationUrlAllowListItem.php
new file mode 100644
index 000000000..54f02ff9f
--- /dev/null
+++ b/src/Components/ApplePayDirect/Models/ApplePayValidationUrlAllowListItem.php
@@ -0,0 +1,50 @@
+value = $value;
+ }
+
+ public static function create(string $value): self
+ {
+ if (empty($value)) {
+ throw new \InvalidArgumentException(sprintf('The value of %s must not be empty', self::class));
+ }
+
+ if (strpos($value, 'http') !== 0) {
+ $value = 'https://' . $value;
+ }
+
+ if (substr($value, -1) !== '/') {
+ $value .= '/';
+ }
+
+ return new self($value);
+ }
+
+ /**
+ * Compare the value with the given value
+ *
+ * @param string $value value that will be compared
+ * @return bool
+ */
+ public function equals(string $value): bool
+ {
+ return $this->value === $value;
+ }
+}
diff --git a/src/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowList.php b/src/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowList.php
new file mode 100644
index 000000000..f7b049de7
--- /dev/null
+++ b/src/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowList.php
@@ -0,0 +1,69 @@
+allowList = $allowList;
+ }
+
+ /**
+ * Create a new ApplePayAllowList
+ *
+ * @param ApplePayValidationUrlAllowListItem ...$items
+ * @return ApplePayValidationUrlAllowList
+ */
+ public static function create(ApplePayValidationUrlAllowListItem ...$items): self
+ {
+ return new self($items);
+ }
+
+ /**
+ * Check if the given value is in the allow list
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function contains(string $value): bool
+ {
+ foreach ($this->allowList as $item) {
+ if ($item->equals($value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEmpty(): bool
+ {
+ return count($this) === 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function count(): int
+ {
+ return count($this->allowList);
+ }
+}
diff --git a/src/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizer.php b/src/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizer.php
new file mode 100644
index 000000000..2e4be7584
--- /dev/null
+++ b/src/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizer.php
@@ -0,0 +1,29 @@
+systemConfigService = $systemConfigService;
+ $this->salesChannelDomainRepository = $salesChannelDomainRepository;
+ }
+
+ /**
+ * Registers the events the subscriber listens to.
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ PluginPostInstallEvent::class => 'setDefaultConfig',
+ PluginPostUpdateEvent::class => 'setDefaultConfig',
+ ];
+ }
+
+ /**
+ * Sets the default configuration for Apple Pay validation allow list.
+ *
+ * This method retrieves all sales channel domains and constructs a comma-separated
+ * list of their URLs. It then sets this list as the default value for the
+ * Apple Pay validation allow list configuration.
+ *
+ * @return void
+ */
+ public function setDefaultConfig(): void
+ {
+ // Check if the Apple Pay validation allow list is already set.
+ if ($this->validationAllowListIsEmpty() === false) {
+ return; // Ensuring to not overwrite the existing configuration.
+ }
+
+ // Create a new context with a system source.
+ $context = new Context(new SystemSource());
+
+ // Define criteria to fetch sales channel domains.
+ $criteria = new Criteria();
+ $criteria->addAssociation('salesChannel');
+
+ // Fetch sales channel domains using the repository.
+ $domains = $this->salesChannelDomainRepository->search($criteria, $context);
+ $usedDomains = [];
+
+ foreach ($domains as $domain) {
+ if (!$domain instanceof SalesChannelDomainEntity) {
+ continue;
+ }
+ $usedDomains[] = $domain->getUrl();
+ }
+
+ if (count($usedDomains)) {
+ // Convert the array of URLs to a comma-separated string.
+ $usedDomainsString = implode(',', $usedDomains);
+
+ // Set the configuration value for the Apple Pay validation allow list.
+ $this->systemConfigService->set('MolliePayments.config.ApplePayValidationAllowList', $usedDomainsString);
+ }
+ }
+
+ /**
+ * Checks if the Apple Pay validation allow list is empty.
+ *
+ * @return bool
+ */
+ private function validationAllowListIsEmpty(): bool
+ {
+ $allowList = $this->systemConfigService->get('MolliePayments.config.ApplePayValidationAllowList');
+ return empty($allowList);
+ }
+}
diff --git a/src/Controller/StoreApi/ApplePayDirect/ApplePayDirectControllerBase.php b/src/Controller/StoreApi/ApplePayDirect/ApplePayDirectControllerBase.php
index a12b367b0..6299b3c35 100644
--- a/src/Controller/StoreApi/ApplePayDirect/ApplePayDirectControllerBase.php
+++ b/src/Controller/StoreApi/ApplePayDirect/ApplePayDirectControllerBase.php
@@ -117,6 +117,7 @@ public function createPaymentSession(RequestDataBag $data, SalesChannelContext $
throw new \Exception('Please provide a validation url!');
}
+ $validationURL = $this->applePay->validateValidationUrl($validationURL);
$session = $this->applePay->createPaymentSession($validationURL, $context);
return new CreateSessionResponse($session);
diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml
index 2184638f7..a9a64eea5 100644
--- a/src/Resources/config/config.xml
+++ b/src/Resources/config/config.xml
@@ -152,6 +152,15 @@
+
+ ApplePayValidationAllowList
+
+
+
+ Enter the domains that are allowed for Apple Pay validation, separated by commas.
+ Geben Sie die Domains ein, die für die Apple Pay Validierung zugelassen sind, getrennt durch Kommas.
+ Voer de domeinen in die zijn toegestaan voor Apple Pay validatie, gescheiden door komma's.
+
createCustomersAtMollie
diff --git a/src/Resources/config/services/components.xml b/src/Resources/config/services/components.xml
index 2d20b69df..88d7a4ae9 100644
--- a/src/Resources/config/services/components.xml
+++ b/src/Resources/config/services/components.xml
@@ -5,6 +5,13 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+
+
+
+
+
+
@@ -39,6 +46,8 @@
+
+
@@ -47,6 +56,13 @@
+
+
+
+
+
+
+
diff --git a/tests/PHPUnit/Components/ApplePayDirect/Gateways/ApplePayValidationUrlAllowListGatewayTest.php b/tests/PHPUnit/Components/ApplePayDirect/Gateways/ApplePayValidationUrlAllowListGatewayTest.php
new file mode 100644
index 000000000..345da8849
--- /dev/null
+++ b/tests/PHPUnit/Components/ApplePayDirect/Gateways/ApplePayValidationUrlAllowListGatewayTest.php
@@ -0,0 +1,36 @@
+service = $this->createMock(SystemConfigService::class);
+ $this->gateway = new ApplePayValidationUrlAllowListGateway($this->service);
+ }
+
+ public function testProvidesEmptyAllowList(): void
+ {
+ $this->service->expects($this->once())->method('get')->willReturn('');
+ $allowList = $this->gateway->getAllowList();
+
+ $this->assertTrue($allowList->isEmpty());
+ }
+
+ public function testProvidesAllowList(): void
+ {
+ $allowListString = 'https://example.com,https://example-url.org';
+ $this->service->expects($this->once())->method('get')->willReturn($allowListString);
+ $allowList = $this->gateway->getAllowList();
+
+ $this->assertFalse($allowList->isEmpty());
+ $this->assertCount(2, $allowList);
+ }
+}
\ No newline at end of file
diff --git a/tests/PHPUnit/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowListTest.php b/tests/PHPUnit/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowListTest.php
new file mode 100644
index 000000000..4653c11e9
--- /dev/null
+++ b/tests/PHPUnit/Components/ApplePayDirect/Models/Collections/ApplePayValidationUrlAllowListTest.php
@@ -0,0 +1,41 @@
+assertTrue($allowList->contains('https://example.com'));
+ $this->assertTrue($allowList->contains('https://example-url.org'));
+ $this->assertFalse($allowList->contains('https://example-url.net'));
+ }
+
+ public function canDetermineIfListIsEmpty(): void
+ {
+ $allowList = ApplePayValidationUrlAllowList::create();
+
+ $this->assertTrue($allowList->isEmpty());
+ }
+
+ public function testProvidesCount(): void
+ {
+ $allowList = ApplePayValidationUrlAllowList::create(
+ ApplePayValidationUrlAllowListItem::create('https://example.com'),
+ ApplePayValidationUrlAllowListItem::create('https://example-url.org')
+ );
+
+ $this->assertCount(2, $allowList);
+ }
+}
\ No newline at end of file
diff --git a/tests/PHPUnit/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizerTest.php b/tests/PHPUnit/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizerTest.php
new file mode 100644
index 000000000..a6b72193d
--- /dev/null
+++ b/tests/PHPUnit/Components/ApplePayDirect/Services/ApplePayValidationUrlSanitizerTest.php
@@ -0,0 +1,37 @@
+sanitizer = new ApplePayValidationUrlSanitizer();
+ }
+
+ /**
+ * @dataProvider sanitationTestDataProvider
+ */
+ public function testProvidesSanitizedUrl(string $url, string $expected): void
+ {
+ $sanitizedUrl = $this->sanitizer->sanitizeValidationUrl($url);
+
+ $this->assertEquals($expected, $sanitizedUrl);
+ }
+
+ public function sanitationTestDataProvider(): array
+ {
+ return [
+ 'keeps http value if provided' => ['http://example.com', 'http://example.com/'],
+ 'keeps https value if provided' => ['https://example.com', 'https://example.com/'],
+ 'adds https to beginning of string if missing' => ['example.com', 'https://example.com/'],
+ 'adds trailing slash if missing' => ['https://example.com', 'https://example.com/'],
+ 'adds https to beginning of string and trailing slash if missing' => ['example.com', 'https://example.com/'],
+ ];
+ }
+}
\ No newline at end of file
diff --git a/tests/PHPUnit/Service/ApplePayDirect/ApplePayDirectTest.php b/tests/PHPUnit/Service/ApplePayDirect/ApplePayDirectTest.php
index 45fbc9fe1..364997d0d 100644
--- a/tests/PHPUnit/Service/ApplePayDirect/ApplePayDirectTest.php
+++ b/tests/PHPUnit/Service/ApplePayDirect/ApplePayDirectTest.php
@@ -3,9 +3,15 @@
namespace Kiener\MolliePayments\Tests\Service\ApplePayDirect;
use Kiener\MolliePayments\Components\ApplePayDirect\ApplePayDirect;
+use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayValidationUrlAllowListCanNotBeEmptyException;
+use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayValidationUrlNotInAllowListException;
+use Kiener\MolliePayments\Components\ApplePayDirect\Gateways\ApplePayValidationUrlAllowListGateway;
+use Kiener\MolliePayments\Components\ApplePayDirect\Models\ApplePayValidationUrlAllowListItem;
+use Kiener\MolliePayments\Components\ApplePayDirect\Models\Collections\ApplePayValidationUrlAllowList;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayDomainVerificationService;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayFormatter;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayShippingBuilder;
+use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayValidationUrlSanitizer;
use Kiener\MolliePayments\Facade\MolliePaymentDoPay;
use Kiener\MolliePayments\Factory\MollieApiFactory;
use Kiener\MolliePayments\Handler\Method\ApplePayPayment;
@@ -41,22 +47,18 @@ class ApplePayDirectTest extends TestCase
{
use MockTrait;
- /**
- * This test verifies that our Apple Pay Cart is correctly
- * built from a provided Shopware Cart object.
- */
- public function testBuildApplePayCart(): void
- {
- $swCart = $this->buildShopwareCart();
+ private SalesChannelContext $scContext;
- /** @var SalesChannelContext $scContext */
- $scContext = $this->createDummyMock(SalesChannelContext::class, $this);
+ private $validationUrlAllowListGateway;
+ private ApplePayDirect $applePay;
+ protected function setUp(): void
+ {
+ $swCart = $this->buildShopwareCart();
- $fakeCartService = new FakeCartService($swCart, $scContext);
+ $this->scContext = $this->createDummyMock(SalesChannelContext::class, $this);
- /** @var ShippingMethodService $shippingMethodService */
- $shippingMethodService = $this->createDummyMock(ShippingMethodService::class, $this);
+ $fakeCartService = new FakeCartService($swCart, $this->scContext);
/** @var ApplePayDomainVerificationService $domainVerification */
$domainVerification = $this->createDummyMock(ApplePayDomainVerificationService::class, $this);
@@ -97,8 +99,11 @@ public function testBuildApplePayCart(): void
/** @var OrderAddressRepository $repoOrderAdresses */
$repoOrderAdresses = $this->createDummyMock(OrderAddressRepository::class, $this);
+ $this->validationUrlAllowListGateway = $this->createDummyMock(ApplePayValidationUrlAllowListGateway::class, $this);
- $applePay = new ApplePayDirect(
+ $validationUrlSanitizer = new ApplePayValidationUrlSanitizer();
+
+ $this->applePay = new ApplePayDirect(
$domainVerification,
$payment,
$doPay,
@@ -112,10 +117,19 @@ public function testBuildApplePayCart(): void
$apiFactory,
$shopService,
$orderService,
- $repoOrderAdresses
+ $repoOrderAdresses,
+ $this->validationUrlAllowListGateway,
+ $validationUrlSanitizer
);
+ }
- $apCart = $applePay->getCart($scContext);
+ /**
+ * This test verifies that our Apple Pay Cart is correctly
+ * built from a provided Shopware Cart object.
+ */
+ public function testBuildApplePayCart(): void
+ {
+ $apCart = $this->applePay->getCart($this->scContext);
$this->assertEquals(34.99, $apCart->getAmount());
$this->assertEquals(5, $apCart->getTaxes()->getPrice());
@@ -131,6 +145,49 @@ public function testBuildApplePayCart(): void
$this->assertEquals(1, $apCart->getShippings()[0]->getQuantity());
}
+ public function testThrowsExceptionWhenAllowListIsEmpty(): void
+ {
+ $this->validationUrlAllowListGateway->expects($this->once())
+ ->method('getAllowList')
+ ->willReturn(ApplePayValidationUrlAllowList::create());
+
+ $this->expectException(ApplePayValidationUrlAllowListCanNotBeEmptyException::class);
+ $this->expectExceptionMessage('The Apple Pay validation URL allow list can not be empty. Please check the configuration.');
+
+ $this->applePay->validateValidationUrl('https://example.com');
+ }
+
+ public function testThrowsExceptionWhenUrlIsNotInAllowList(): void
+ {
+ $allowList = ApplePayValidationUrlAllowList::create(
+ ApplePayValidationUrlAllowListItem::create('https://example.com/')
+ );
+ $this->validationUrlAllowListGateway->expects($this->once())
+ ->method('getAllowList')
+ ->willReturn($allowList);
+
+ $testUrl = 'https://example.org/';
+
+ $this->expectException(ApplePayValidationUrlNotInAllowListException::class);
+ $this->expectExceptionMessage(sprintf('The given URL %s is not in the Apple Pay validation URL allow list.', $testUrl));
+
+ $this->applePay->validateValidationUrl($testUrl);
+ }
+
+ public function testProvidesValidValidationUrl(): void
+ {
+ $allowList = ApplePayValidationUrlAllowList::create(
+ ApplePayValidationUrlAllowListItem::create($expected = 'https://example.com/')
+ );
+
+ $this->validationUrlAllowListGateway->expects($this->once())
+ ->method('getAllowList')
+ ->willReturn($allowList);
+
+ $actual = $this->applePay->validateValidationUrl($expected);
+
+ $this->assertSame($expected, $actual);
+ }
/**
* @return Cart