Skip to content

Commit

Permalink
PISHPS-328: added ApplePayDirectDomainAllowList (#790)
Browse files Browse the repository at this point in the history
* PISHPS-328: added ApplePayValidationAllowList, subscriber to list default saleschannel urls, integrated allow list into applepay validate route

* PISHPS-303: applied requested todos

* PISHPS-328: applied requested todos

* PISHPS-328: added test case to ApplePayDirectDomainSanitizerTest for sub domains
  • Loading branch information
m-muxfeld-diw authored Jul 26, 2024
1 parent 99f0e81 commit 39ebd2e
Show file tree
Hide file tree
Showing 16 changed files with 572 additions and 25 deletions.
67 changes: 59 additions & 8 deletions src/Components/ApplePayDirect/ApplePayDirect.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace Kiener\MolliePayments\Components\ApplePayDirect;

use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayDirectDomainAllowListCanNotBeEmptyException;
use Kiener\MolliePayments\Components\ApplePayDirect\Exceptions\ApplePayDirectDomainNotInAllowListException;
use Kiener\MolliePayments\Components\ApplePayDirect\Gateways\ApplePayDirectDomainAllowListGateway;
use Kiener\MolliePayments\Components\ApplePayDirect\Models\ApplePayCart;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayDirectDomainSanitizer;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayDomainVerificationService;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayFormatter;
use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayShippingBuilder;
Expand Down Expand Up @@ -105,6 +109,16 @@ class ApplePayDirect
*/
private $repoOrderAdresses;

/**
* @var ApplePayDirectDomainAllowListGateway
*/
private $applePayDirectDomainAllowListGateway;

/**
* @var ApplePayDirectDomainSanitizer
*/
private $domainSanitizer;


/**
* @param ApplePayDomainVerificationService $domainFileDownloader
Expand All @@ -121,8 +135,10 @@ class ApplePayDirect
* @param ShopService $shopService
* @param OrderService $orderService
* @param OrderAddressRepositoryInterface $repoOrderAdresses
* @param ApplePayDirectDomainAllowListGateway $domainAllowListGateway
* @param ApplePayDirectDomainSanitizer $domainSanitizer
*/
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, ApplePayDirectDomainAllowListGateway $domainAllowListGateway, ApplePayDirectDomainSanitizer $domainSanitizer)
{
$this->domainFileDownloader = $domainFileDownloader;
$this->paymentHandler = $paymentHandler;
Expand All @@ -138,6 +154,8 @@ public function __construct(ApplePayDomainVerificationService $domainFileDownloa
$this->shopService = $shopService;
$this->orderService = $orderService;
$this->repoOrderAdresses = $repoOrderAdresses;
$this->applePayDirectDomainAllowListGateway = $domainAllowListGateway;
$this->domainSanitizer = $domainSanitizer;
}


Expand Down Expand Up @@ -261,18 +279,16 @@ public function setShippingMethod(string $shippingMethodID, SalesChannelContext

/**
* @param string $validationURL
* @param string $domain
* @param SalesChannelContext $context
* @throws ApiException
* @throws ApplePayDirectDomainAllowListCanNotBeEmptyException
* @throws ApplePayDirectDomainNotInAllowListException
* @return string
*/
public function createPaymentSession(string $validationURL, SalesChannelContext $context): string
public function createPaymentSession(string $validationURL, string $domain, SalesChannelContext $context): string
{
# make sure to get rid of any http prefixes or
# also any sub shop slugs like /de or anything else
# that would NOT work with Mollie and Apple Pay!
$domainExtractor = new DomainExtractor();
$domain = $domainExtractor->getCleanDomain($this->shopService->getShopUrl(true));

$domain = $this->getValidDomain($domain, $context);
# we always have to use the LIVE api key for
# our first domain validation for Apple Pay!
# the rest will be done with our test API key (if test mode active), or also Live API key (no test mode)
Expand Down Expand Up @@ -507,4 +523,39 @@ private function buildApplePayCart(Cart $cart): ApplePayCart

return $appleCart;
}

/**
* This method will return a valid domain if not provided by the user it will use the shop domain
*
* @param SalesChannelContext $context
* @param string $domain
* @throws ApplePayDirectDomainAllowListCanNotBeEmptyException
* @throws ApplePayDirectDomainNotInAllowListException
* @return string
*/
private function getValidDomain(string $domain, SalesChannelContext $context): string
{
# if we have no domain, then we need to use the shop domain
if (empty($domain)) {
# make sure to get rid of any http prefixes or
# also any sub shop slugs like /de or anything else
# that would NOT work with Mollie and Apple Pay!
$domainExtractor = new DomainExtractor();
return $domainExtractor->getCleanDomain($this->shopService->getShopUrl(true));
}

$allowList = $this->applePayDirectDomainAllowListGateway->getAllowList($context);

if ($allowList->isEmpty()) {
throw new ApplePayDirectDomainAllowListCanNotBeEmptyException();
}

$sanitizedDomain = $this->domainSanitizer->sanitizeDomain($domain);

if ($allowList->contains($sanitizedDomain) === false) {
throw new ApplePayDirectDomainNotInAllowListException($sanitizedDomain);
}

return $sanitizedDomain;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Exceptions;

class ApplePayDirectDomainAllowListCanNotBeEmptyException extends \Exception
{
public function __construct()
{
parent::__construct('The Apple Pay Direct domain allow list can not be empty. Please check the configuration.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Exceptions;

class ApplePayDirectDomainNotInAllowListException extends \Exception
{
public function __construct(string $url)
{
parent::__construct(sprintf('The given URL %s is not in the Apple Pay Direct domain allow list.', $url));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Gateways;

use Kiener\MolliePayments\Components\ApplePayDirect\Models\ApplePayDirectDomainAllowListItem;
use Kiener\MolliePayments\Components\ApplePayDirect\Models\Collections\ApplePayDirectDomainAllowList;
use Kiener\MolliePayments\Service\SettingsService;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

class ApplePayDirectDomainAllowListGateway
{
/**
* @var SettingsService
*/
private $settingsService;

/**
* ApplePayDirectDomainAllowListItem constructor.
*
* @param SettingsService $settingsService
*/
public function __construct(SettingsService $settingsService)
{
$this->settingsService = $settingsService;
}

/**
* Get the ApplePayDirectDomainAllowList
*
* @param SalesChannelContext $context
* @return ApplePayDirectDomainAllowList
*/
public function getAllowList(SalesChannelContext $context): ApplePayDirectDomainAllowList
{
$settings = $this->settingsService->getSettings($context->getSalesChannel()->getId());
$allowList = $settings->getApplePayDirectDomainAllowList();

if (empty($allowList)) {
return ApplePayDirectDomainAllowList::create();
}

$allowList = trim($allowList);

$items = explode(',', $allowList);
$items = array_map([ApplePayDirectDomainAllowListItem::class, 'create'], $items);

return ApplePayDirectDomainAllowList::create(...$items);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Models;

use Kiener\MolliePayments\Components\ApplePayDirect\Services\ApplePayDirectDomainSanitizer;

class ApplePayDirectDomainAllowListItem
{
/**
* @var string
*/
private $value;

/**
* ApplePayDirectDomainAllowListItem constructor.
*
* @param string $value
*/
private function __construct(string $value)
{
$this->value = $value;
}

/**
* Create a new ApplePayDirectDomainAllowListItem
*
* @param string $value
* @return self
*/
public static function create(string $value): self
{
if (empty($value)) {
throw new \InvalidArgumentException(sprintf('The value of %s must not be empty', self::class));
}

$value = (new ApplePayDirectDomainSanitizer())->sanitizeDomain($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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Models\Collections;

use Countable;
use Kiener\MolliePayments\Components\ApplePayDirect\Models\ApplePayDirectDomainAllowListItem;

class ApplePayDirectDomainAllowList implements Countable
{
/**
* @var ApplePayDirectDomainAllowListItem[]
*/
private $allowList;

/**
* ApplePayDirectDomainAllowList constructor.
*
* @param ApplePayDirectDomainAllowListItem[] $allowList
*/
private function __construct(array $allowList = [])
{
$this->allowList = $allowList;
}

/**
* Create a new ApplePayDirectDomainAllowList
*
* @param ApplePayDirectDomainAllowListItem ...$items
* @return ApplePayDirectDomainAllowList
*/
public static function create(ApplePayDirectDomainAllowListItem ...$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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Components\ApplePayDirect\Services;

/**
* This class is responsible for sanitizing the given domain
*/
class ApplePayDirectDomainSanitizer
{
/**
* Sanitize the given domain
*
* @param string $domain
* @return string
*/
public function sanitizeDomain(string $domain): string
{
# we need to have a protocol before the parse url command
# in order to have it work correctly
if (strpos($domain, 'http') !== 0) {
$domain = 'https://' . $domain;
}

# now extract the raw domain without protocol
# and without any sub shop urls
return (string)parse_url($domain, PHP_URL_HOST);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ public function addProduct(RequestDataBag $data, SalesChannelContext $context):
public function createPaymentSession(RequestDataBag $data, SalesChannelContext $context): StoreApiResponse
{
$validationURL = $data->get('validationUrl');
$domain = (string)$data->get('domain');

if (empty($validationURL)) {
throw new \Exception('Please provide a validation url!');
}

$session = $this->applePay->createPaymentSession($validationURL, $context);
$session = $this->applePay->createPaymentSession($validationURL, $domain, $context);

return new CreateSessionResponse($session);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public function createPaymentSession(SalesChannelContext $context, Request $requ

$validationURL = (string)$content['validationUrl'];

$session = $this->applePay->createPaymentSession($validationURL, $context);
$session = $this->applePay->createPaymentSession($validationURL, '', $context);

return new JsonResponse([
'session' => $session,
Expand Down
22 changes: 22 additions & 0 deletions src/Resources/config/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,28 @@
</option>
</options>
</input-field>
<input-field type="text">
<name>applePayDirectDomainAllowList</name>
<label>Allowed Domains for Apple Pay Direct</label>
<label lang="de-DE">Zulässige Domains für die Apple Pay Direct</label>
<label lang="nl-NL">Toegestane domeinen voor Apple Pay Direct</label>
<helpText>
Enter the domains that are allowed for Apple Pay Direct, separated by commas.
This setting is optional and only required if you are using a headless setup
and want to provide an alternative domain for your Apple Pay Direct integration.
</helpText>
<helpText lang="de-DE">
Geben Sie die Domains ein, die für die Apple Pay Direct zugelassen sind, getrennt durch Kommas.
Diese Einstellung ist optional und nur erforderlich, wenn Sie ein Headless-Setup verwenden
und eine alternative Domain für Ihre Apple Pay Direct Integration bereitstellen möchten.
</helpText>
<helpText lang="nl-NL">
Voer de domeinen in die zijn toegestaan voor Apple Pay Direct, gescheiden door komma's.
Deze instelling is optioneel en alleen nodig als u een headless setup gebruikt
en een alternatieve domein voor uw Apple Pay Direct integratie wilt opgeven.
</helpText>
</input-field>

<input-field type="bool">
<name>createCustomersAtMollie</name>
<label>Create customers at Mollie</label>
Expand Down
Loading

0 comments on commit 39ebd2e

Please sign in to comment.