From d2ca6d5483f619b312742ea3dd6fc1cbaae3e006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20M=C3=BCller?= Date: Mon, 14 Oct 2024 21:30:05 +0200 Subject: [PATCH] PAYOSWXP-158: Add PayPal v2 payment methods --- .../CustomerRegistrationUtil.php | 113 +++++--- .../Helper/ActivePaymentMethodsLoader.php | 72 +++++ .../ActivePaymentMethodsLoaderInterface.php | 15 + src/Configuration/ConfigurationPrefixes.php | 4 + src/Controller/SettingsController.php | 23 ++ src/Core/Utils/AddressCompare.php | 62 +++-- .../handler/payment_handler.xml | 12 + src/DependencyInjection/listeners.xml | 12 +- .../payment_method_filter.xml | 7 + .../requestParameter/builder.xml | 38 +++ src/DependencyInjection/services.xml | 7 + .../PayPalV2ExpressEventListener.php | 101 +++++++ .../StorefrontRenderEventListener.php | 67 +---- src/Installer/PaymentMethodInstaller.php | 7 + src/PaymentHandler/PaymentHandlerGroups.php | 2 + .../PayonePaypalV2ExpressPaymentHandler.php | 16 ++ .../PayonePaypalV2PaymentHandler.php | 66 +++++ src/PaymentMethod/PayonePaypalV2.php | 38 +++ src/PaymentMethod/PayonePaypalV2Express.php | 38 +++ .../CustomerRequestParameterBuilder.php | 4 + .../AuthorizeRequestParameterBuilder.php | 37 +++ .../PreAuthorizeRequestParameterBuilder.php | 34 +++ .../AuthorizeRequestParameterBuilder.php | 25 ++ .../CreateCheckoutSessionParameterBuilder.php | 31 +++ ...CheckoutDetailsRequestParameterBuilder.php | 31 +++ .../PreAuthorizeRequestParameterBuilder.php | 34 +++ .../ReturnUrlRequestParameterBuilder.php | 4 + ...pingInformationRequestParameterBuilder.php | 4 + .../module/payone-payment/snippet/de_DE.json | 2 + .../module/payone-payment/snippet/en_GB.json | 2 + .../js/payone-payment/payone-payment.js | 2 +- src/Resources/app/storefront/src/main.js | 2 + .../src/paypal-v2/payone-payment.paypal-v2.js | 92 ++++++ src/Resources/config/settings.xml | 84 ++++++ .../administration/js/payone-payment.js | 2 +- .../checkout/offcanvas-cart.html.twig | 10 +- .../checkout/paypal-v2-button.html.twig | 13 + .../storefront/page/account/sidebar.html.twig | 2 +- .../page/checkout/address/index.html.twig | 12 +- .../page/checkout/cart/index.html.twig | 10 +- .../Controller/GenericExpressController.php | 46 +++ .../Struct/PayPalV2ExpressButtonData.php | 82 ++++++ .../CustomerRegistrationUtilTest.php | 263 +++++++++++------- .../Helper/ActivePaymentMethodsLoaderTest.php | 196 +++++++++++++ .../RedirectHandler/RedirectHandlerTest.php | 11 +- .../PayPalV2ExpressEventListenerTest.php | 224 +++++++++++++++ ...teCheckoutSessionRequestParametersTest.php | 49 ++++ ...etCheckoutSessionRequestParametersTest.php | 39 +++ .../PayonePaypalV2PaymentHandlerTest.php | 107 +++++++ .../AuthorizeRequestParameterBuilderTest.php | 56 ++++ 50 files changed, 1967 insertions(+), 243 deletions(-) create mode 100644 src/Components/Helper/ActivePaymentMethodsLoader.php create mode 100644 src/Components/Helper/ActivePaymentMethodsLoaderInterface.php create mode 100644 src/EventListener/PayPalV2ExpressEventListener.php create mode 100644 src/PaymentHandler/PayonePaypalV2ExpressPaymentHandler.php create mode 100644 src/PaymentHandler/PayonePaypalV2PaymentHandler.php create mode 100644 src/PaymentMethod/PayonePaypalV2.php create mode 100644 src/PaymentMethod/PayonePaypalV2Express.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2/AuthorizeRequestParameterBuilder.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2/PreAuthorizeRequestParameterBuilder.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2Express/AuthorizeRequestParameterBuilder.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2Express/CreateCheckoutSessionParameterBuilder.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2Express/GetCheckoutDetailsRequestParameterBuilder.php create mode 100644 src/Payone/RequestParameter/Builder/PaypalV2Express/PreAuthorizeRequestParameterBuilder.php create mode 100644 src/Resources/app/storefront/src/paypal-v2/payone-payment.paypal-v2.js create mode 100644 src/Resources/views/storefront/component/checkout/paypal-v2-button.html.twig create mode 100644 src/Storefront/Struct/PayPalV2ExpressButtonData.php create mode 100644 tests/Components/Helper/ActivePaymentMethodsLoaderTest.php create mode 100644 tests/EventListener/PayPalV2ExpressEventListenerTest.php create mode 100644 tests/Functional/Payment/PaypalV2Express/CreateCheckoutSessionRequestParametersTest.php create mode 100644 tests/Functional/Payment/PaypalV2Express/GetCheckoutSessionRequestParametersTest.php create mode 100644 tests/PaymentHandler/PayonePaypalV2PaymentHandlerTest.php create mode 100644 tests/Payone/RequestParameter/Builder/PaypalV2/AuthorizeRequestParameterBuilderTest.php diff --git a/src/Components/GenericExpressCheckout/CustomerRegistrationUtil.php b/src/Components/GenericExpressCheckout/CustomerRegistrationUtil.php index 090dd3888..6175f154c 100644 --- a/src/Components/GenericExpressCheckout/CustomerRegistrationUtil.php +++ b/src/Components/GenericExpressCheckout/CustomerRegistrationUtil.php @@ -4,6 +4,7 @@ namespace PayonePayment\Components\GenericExpressCheckout; +use PayonePayment\Core\Utils\AddressCompare; use RuntimeException; use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Framework\Context; @@ -28,40 +29,55 @@ public function getCustomerDataBagFromGetCheckoutSessionResponse(array $response { $salutationId = $this->getSalutationId($context); + $billingAddress = array_filter([ + 'salutationId' => $salutationId, + 'company' => $this->extractBillingData($response, 'company'), + 'firstName' => $this->extractBillingData($response, 'firstname'), + 'lastName' => $this->extractBillingData($response, 'lastname'), + 'street' => $this->extractBillingData($response, 'street'), + 'additionalAddressLine1' => $this->extractBillingData($response, 'addressaddition'), + 'zipcode' => $this->extractBillingData($response, 'zip'), + 'city' => $this->extractBillingData($response, 'city'), + 'countryId' => $this->getCountryIdByCode($this->extractBillingData($response, 'country') ?? '', $context), + 'phone' => $this->extractBillingData($response, 'telephonenumber'), + ]); + + $shippingAddress = array_filter([ + 'salutationId' => $salutationId, + 'company' => $this->extractShippingData($response, 'company'), + 'firstName' => $this->extractShippingData($response, 'firstname'), + 'lastName' => $this->extractShippingData($response, 'lastname'), + 'street' => $this->extractShippingData($response, 'street'), + 'additionalAddressLine1' => $this->extractShippingData($response, 'addressaddition'), + 'zipcode' => $this->extractShippingData($response, 'zip'), + 'city' => $this->extractShippingData($response, 'city'), + 'countryId' => $this->getCountryIdByCode($this->extractShippingData($response, 'country') ?? '', $context), + 'phone' => $this->extractShippingData($response, 'telephonenumber'), + ]); + + $isBillingAddressComplete = $this->hasAddressRequiredData($billingAddress); + $isShippingAddressComplete = $this->hasAddressRequiredData($shippingAddress); + + if (!$isBillingAddressComplete && !$isShippingAddressComplete) { + throw new RuntimeException($this->translator->trans('PayonePayment.errorMessages.genericError')); + } + + if (!$isBillingAddressComplete && $isShippingAddressComplete) { + $billingAddress = $shippingAddress; + } + $customerData = new RequestDataBag([ 'guest' => true, 'salutationId' => $salutationId, 'email' => $response['addpaydata']['email'], - 'firstName' => $this->extractBillingData($response, 'firstname'), - 'lastName' => $this->extractBillingData($response, 'lastname'), + 'firstName' => $billingAddress['firstName'], /** @phpstan-ignore offsetAccess.notFound */ + 'lastName' => $billingAddress['lastName'], /** @phpstan-ignore offsetAccess.notFound */ 'acceptedDataProtection' => true, - 'billingAddress' => array_filter([ - 'salutationId' => $salutationId, - 'company' => $this->extractBillingData($response, 'company'), - 'firstName' => $this->extractBillingData($response, 'firstname'), - 'lastName' => $this->extractBillingData($response, 'lastname'), - 'street' => $this->extractBillingData($response, 'street'), - 'additionalAddressLine1' => $this->extractBillingData($response, 'addressaddition'), - 'zipcode' => $this->extractBillingData($response, 'zip'), - 'city' => $this->extractBillingData($response, 'city'), - 'countryId' => $this->getCountryIdByCode($this->extractBillingData($response, 'country') ?? '', $context), - 'phone' => $this->extractBillingData($response, 'telephonenumber'), - ]), - 'shippingAddress' => array_filter([ - 'salutationId' => $salutationId, - 'company' => $this->extractShippingData($response, 'company'), - 'firstName' => $this->extractShippingData($response, 'firstname'), - 'lastName' => $this->extractShippingData($response, 'lastname'), - 'street' => $this->extractShippingData($response, 'street'), - 'additionalAddressLine1' => $this->extractShippingData($response, 'addressaddition'), - 'zipcode' => $this->extractShippingData($response, 'zip'), - 'city' => $this->extractShippingData($response, 'city'), - 'countryId' => $this->getCountryIdByCode($this->extractShippingData($response, 'country') ?? '', $context), - 'phone' => $this->extractShippingData($response, 'telephonenumber'), - ]), + 'billingAddress' => $billingAddress, + 'shippingAddress' => $shippingAddress, ]); - if ($this->extractBillingData($response, 'company') !== null) { + if ($customerData->get('billingAddress')?->get('company') !== null) { $customerData->set('accountType', CustomerEntity::ACCOUNT_TYPE_BUSINESS); } else { $customerData->set('accountType', CustomerEntity::ACCOUNT_TYPE_PRIVATE); @@ -69,16 +85,16 @@ public function getCustomerDataBagFromGetCheckoutSessionResponse(array $response $billingAddress = $customerData->get('billingAddress')?->all() ?: []; $shippingAddress = $customerData->get('shippingAddress')?->all() ?: []; - if (array_diff($billingAddress, $shippingAddress) === []) { + if (!$isShippingAddressComplete || AddressCompare::areRawAddressesIdentical($billingAddress, $shippingAddress)) { $customerData->remove('shippingAddress'); } return $customerData; } - private function extractBillingData(array $response, string $key, string|null $alternateKey = null): ?string + private function extractBillingData(array $response, string $key): ?string { - // special case: PayPal express: PayPal does not return firstname. so we need to take the firstname from the shipping-data + // special case: PayPal v1 express: PayPal does not return firstname. so we need to take the firstname from the shipping-data if (($key === 'firstname' || $key === 'lastname') && !\array_key_exists('firstname', $response['addpaydata']) && isset( @@ -92,20 +108,16 @@ private function extractBillingData(array $response, string $key, string|null $a } } - if ($alternateKey === null - && !\array_key_exists('billing_lastname', $response['addpaydata']) - && !\array_key_exists('lastname', $response['addpaydata']) - ) { - // there are no explicit billing-address-details. We assume that there are only shipping details. So we use the shipping details for the billing details too. - $alternateKey = 'shipping_' . $key; - } - - return $response['addpaydata']['billing_' . $key] ?? $response['addpaydata'][$key] ?? ($alternateKey ? $response['addpaydata'][$alternateKey] : null); + // Do not take any values from the shipping address as a fallback for individual fields. + // If mandatory fields are missing from the billing address, the complete shipping address is used + return $response['addpaydata']['billing_' . $key] ?? $response['addpaydata'][$key] ?? null; } - private function extractShippingData(array $response, string $key, ?string $alternateKey = null): ?string + private function extractShippingData(array $response, string $key): ?string { - return $response['addpaydata']['shipping_' . $key] ?? $response['addpaydata'][$key] ?? $response['addpaydata'][$alternateKey] ?? $this->extractBillingData($response, $key); + // Do not take any values from the billing address as a fallback for individual fields. + // If mandatory fields are missing from the shipping address, the complete shipping address is removed + return $response['addpaydata']['shipping_' . $key] ?? null; } private function getSalutationId(Context $context): string @@ -145,4 +157,23 @@ private function getCountryIdByCode(string $code, Context $context): ?string return $country->getId(); } + + private function hasAddressRequiredData(array $address): bool + { + $requiredFields = [ + 'firstName', + 'lastName', + 'city', + 'street', + 'countryId', + ]; + + foreach ($requiredFields as $field) { + if (!isset($address[$field])) { + return false; + } + } + + return true; + } } diff --git a/src/Components/Helper/ActivePaymentMethodsLoader.php b/src/Components/Helper/ActivePaymentMethodsLoader.php new file mode 100644 index 000000000..7c63130ec --- /dev/null +++ b/src/Components/Helper/ActivePaymentMethodsLoader.php @@ -0,0 +1,72 @@ +generateCacheKey($salesChannelContext->getSalesChannelId()); + + $cacheItem = $this->cachePool->getItem($cacheKey); + + if ($cacheItem->get() === null) { + $cacheItem->set($this->collectActivePayonePaymentMethodIds($salesChannelContext)); + + $this->cachePool->save($cacheItem); + } + + return $cacheItem->get(); + } + + public function clearCache(Context $context): void + { + $cacheKeys = []; + + /** @var string[] $salesChannelIds */ + $salesChannelIds = $this->salesChannelRepository->searchIds(new Criteria(), $context)->getIds(); + + foreach ($salesChannelIds as $salesChannelId) { + $cacheKeys[] = $this->generateCacheKey($salesChannelId); + } + + if ($cacheKeys === []) { + return; + } + + $this->cachePool->deleteItems($cacheKeys); + } + + private function collectActivePayonePaymentMethodIds(SalesChannelContext $salesChannelContext): array + { + $criteria = new Criteria(); + + $criteria->addFilter(new ContainsFilter('handlerIdentifier', 'PayonePayment')); + $criteria->addFilter(new EqualsFilter('active', true)); + + return $this->paymentMethodRepository->searchIds($criteria, $salesChannelContext)->getIds(); + } + + private function generateCacheKey(string $salesChannelId): string + { + return 'payone_payment.active_payment_methods.' . $salesChannelId; + } +} diff --git a/src/Components/Helper/ActivePaymentMethodsLoaderInterface.php b/src/Components/Helper/ActivePaymentMethodsLoaderInterface.php new file mode 100644 index 000000000..a2898319f --- /dev/null +++ b/src/Components/Helper/ActivePaymentMethodsLoaderInterface.php @@ -0,0 +1,15 @@ + self::CONFIGURATION_PREFIX_DEBIT, Handler\PayonePaypalPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYPAL, Handler\PayonePaypalExpressPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYPAL_EXPRESS, + Handler\PayonePaypalV2PaymentHandler::class => self::CONFIGURATION_PREFIX_PAYPAL_V2, + Handler\PayonePaypalV2ExpressPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYPAL_V2_EXPRESS, Handler\PayonePayolutionInvoicingPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYOLUTION_INVOICING, Handler\PayonePayolutionInstallmentPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYOLUTION_INSTALLMENT, Handler\PayonePayolutionDebitPaymentHandler::class => self::CONFIGURATION_PREFIX_PAYOLUTION_DEBIT, diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php index 745021a28..c21ce0c92 100644 --- a/src/Controller/SettingsController.php +++ b/src/Controller/SettingsController.php @@ -172,6 +172,29 @@ private function getPaymentParameters(string $paymentClass): array 'successurl' => 'https://www.payone.com', ]; + case Handler\PayonePaypalV2ExpressPaymentHandler::class: + case Handler\PayonePaypalV2PaymentHandler::class: + return [ + 'request' => 'preauthorization', + 'clearingtype' => 'wlt', + 'wallettype' => 'PAL', + 'amount' => 100, + 'currency' => 'EUR', + 'reference' => sprintf('%s%d', self::REFERENCE_PREFIX_TEST, random_int(1_000_000_000_000, 9_999_999_999_999)), + 'firstname' => 'Test', + 'lastname' => 'Test', + 'country' => 'DE', + 'successurl' => 'https://www.payone.com', + 'errorurl' => 'https://www.payone.com', + 'backurl' => 'https://www.payone.com', + 'shipping_city' => 'Berlin', + 'shipping_country' => 'DE', + 'shipping_firstname' => 'Test', + 'shipping_lastname' => 'Test', + 'shipping_street' => 'Mustergasse 5', + 'shipping_zip' => '10969', + ]; + case Handler\PayoneSofortBankingPaymentHandler::class: return [ 'request' => 'preauthorization', diff --git a/src/Core/Utils/AddressCompare.php b/src/Core/Utils/AddressCompare.php index 36b339f54..95b6aae21 100644 --- a/src/Core/Utils/AddressCompare.php +++ b/src/Core/Utils/AddressCompare.php @@ -10,42 +10,33 @@ class AddressCompare { + private const ADDRESS_FIELDS = [ + 'firstName', + 'lastName', + 'salutationId', + 'company', + 'street', + 'additionalAddressLine1', + 'additionalAddressLine2', + 'zipcode', + 'city', + 'countryId', + 'countryStateId', + ]; + public static function areOrderAddressesIdentical(OrderAddressEntity $entity1, OrderAddressEntity $entity2): bool { - $fieldsToCompare = [ - 'firstName', - 'lastName', - 'salutationId', - 'company', - 'street', - 'additionalAddressLine1', - 'additionalAddressLine2', - 'zipcode', - 'city', - 'countryId', - 'countryStateId', - ]; - - return self::areEntitiesIdentical($entity1, $entity2, $fieldsToCompare); + return self::areEntitiesIdentical($entity1, $entity2, self::ADDRESS_FIELDS); } public static function areCustomerAddressesIdentical(CustomerAddressEntity $entity1, CustomerAddressEntity $entity2): bool { - $fieldsToCompare = [ - 'firstName', - 'lastName', - 'salutationId', - 'company', - 'street', - 'additionalAddressLine1', - 'additionalAddressLine2', - 'zipcode', - 'city', - 'countryId', - 'countryStateId', - ]; - - return self::areEntitiesIdentical($entity1, $entity2, $fieldsToCompare); + return self::areEntitiesIdentical($entity1, $entity2, self::ADDRESS_FIELDS); + } + + public static function areRawAddressesIdentical(array $address1, array $address2): bool + { + return self::areArraysIdentical($address1, $address2, self::ADDRESS_FIELDS); } private static function areEntitiesIdentical(Entity $entity1, Entity $entity2, array $fields): bool @@ -58,4 +49,15 @@ private static function areEntitiesIdentical(Entity $entity1, Entity $entity2, a return true; } + + private static function areArraysIdentical(array $array1, array $array2, array $fields): bool + { + foreach ($fields as $field) { + if (($array1[$field] ?? null) !== ($array2[$field] ?? null)) { + return false; + } + } + + return true; + } } diff --git a/src/DependencyInjection/handler/payment_handler.xml b/src/DependencyInjection/handler/payment_handler.xml index 2f4e90c5e..8dbdc0dbf 100644 --- a/src/DependencyInjection/handler/payment_handler.xml +++ b/src/DependencyInjection/handler/payment_handler.xml @@ -164,6 +164,18 @@ + + + + + + + + + + diff --git a/src/DependencyInjection/listeners.xml b/src/DependencyInjection/listeners.xml index e4a92f4d0..5a3b3c7b7 100644 --- a/src/DependencyInjection/listeners.xml +++ b/src/DependencyInjection/listeners.xml @@ -36,9 +36,7 @@ - - - + @@ -76,6 +74,14 @@ + + + + + + + + diff --git a/src/DependencyInjection/payment_method_filter.xml b/src/DependencyInjection/payment_method_filter.xml index 58c61aade..341b0eefa 100644 --- a/src/DependencyInjection/payment_method_filter.xml +++ b/src/DependencyInjection/payment_method_filter.xml @@ -186,6 +186,13 @@ + + PayonePayment\PaymentHandler\PayonePaypalV2ExpressPaymentHandler + + + diff --git a/src/DependencyInjection/requestParameter/builder.xml b/src/DependencyInjection/requestParameter/builder.xml index c9f2485d4..cc2d953cd 100644 --- a/src/DependencyInjection/requestParameter/builder.xml +++ b/src/DependencyInjection/requestParameter/builder.xml @@ -123,6 +123,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DependencyInjection/services.xml b/src/DependencyInjection/services.xml index b33344bdd..52779345c 100644 --- a/src/DependencyInjection/services.xml +++ b/src/DependencyInjection/services.xml @@ -50,6 +50,13 @@ + + + + + + + diff --git a/src/EventListener/PayPalV2ExpressEventListener.php b/src/EventListener/PayPalV2ExpressEventListener.php new file mode 100644 index 000000000..23ff86ebb --- /dev/null +++ b/src/EventListener/PayPalV2ExpressEventListener.php @@ -0,0 +1,101 @@ + 'addExpressCheckoutDataToPage', + CheckoutRegisterPageLoadedEvent::class => 'addExpressCheckoutDataToPage', + OffcanvasCartPageLoadedEvent::class => 'addExpressCheckoutDataToPage', + ]; + } + + public function addExpressCheckoutDataToPage(PageLoadedEvent $event): void + { + $salesChannelContext = $event->getSalesChannelContext(); + $activePaymentMethodIds = $this->activePaymentMethodsLoader->getActivePaymentMethodIds($salesChannelContext); + if (!\in_array(PayonePaypalV2Express::UUID, $activePaymentMethodIds, true)) { + return; + } + + $request = $event->getRequest(); + $config = $this->configReader->read($salesChannelContext->getSalesChannelId()); + $isSandbox = $config->get('transactionMode') === 'test'; + $merchantId = $isSandbox ? self::SANDBOX_MERCHANT_ID : $config->get('paypalV2ExpressPayPalMerchantId'); + if (!\is_string($merchantId) || $merchantId === '') { + $this->logger->warning('The payment method “PAYONE PayPal Express v2” is active, but the configuration “PayPal Merchant ID (Live)” is missing!'); + + return; + } + + $struct = new PayPalV2ExpressButtonData(); + + $struct->assign([ + 'sandbox' => $isSandbox, + 'clientId' => $isSandbox ? self::SANDBOX_CLIENT_ID : self::LIVE_CLIENT_ID, + 'merchantId' => $merchantId, + 'currency' => $salesChannelContext->getCurrency()->getIsoCode(), + 'locale' => str_replace('-', '_', $request->getLocale()), + 'showPayLaterButton' => $config->getBool('paypalV2ExpressShowPayLaterButton'), + 'createCheckoutSessionUrl' => $this->router->generate( + 'frontend.account.payone.express-checkout.generic.create-session', + [ + 'paymentMethodId' => PayonePaypalV2Express::UUID, + ] + ), + 'onApproveRedirectUrl' => $this->router->generate( + 'frontend.account.payone.express-checkout.generic.return', + [ + 'paymentMethodId' => PayonePaypalV2Express::UUID, + 'state' => GenericExpressController::STATE_SUCCESS, + ] + ), + 'onCancelRedirectUrl' => $this->router->generate( + 'frontend.account.payone.express-checkout.generic.return', + [ + 'paymentMethodId' => PayonePaypalV2Express::UUID, + 'state' => GenericExpressController::STATE_CANCEL, + ] + ), + 'onErrorRedirectUrl' => $this->router->generate( + 'frontend.account.payone.express-checkout.generic.return', + [ + 'paymentMethodId' => PayonePaypalV2Express::UUID, + 'state' => GenericExpressController::STATE_ERROR, + ] + ), + ]); + + $event->getPage()->addExtension(PayPalV2ExpressButtonData::EXTENSION_NAME, $struct); + } +} diff --git a/src/EventListener/StorefrontRenderEventListener.php b/src/EventListener/StorefrontRenderEventListener.php index 7e46ee298..fdd171e42 100644 --- a/src/EventListener/StorefrontRenderEventListener.php +++ b/src/EventListener/StorefrontRenderEventListener.php @@ -4,27 +4,18 @@ namespace PayonePayment\EventListener; +use PayonePayment\Components\Helper\ActivePaymentMethodsLoaderInterface; use PayonePayment\Installer\PaymentMethodInstaller; -use Psr\Cache\CacheItemPoolInterface; use Shopware\Core\Checkout\Payment\PaymentMethodDefinition; -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelPaymentMethod\SalesChannelPaymentMethodDefinition; -use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository; -use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Event\StorefrontRenderEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class StorefrontRenderEventListener implements EventSubscriberInterface { public function __construct( - private readonly CacheItemPoolInterface $cachePool, - private readonly SalesChannelRepository $paymentMethodRepository, - private readonly EntityRepository $salesChannelRepository + private readonly ActivePaymentMethodsLoaderInterface $activePaymentMethodsLoader, ) { } @@ -38,23 +29,10 @@ public static function getSubscribedEvents(): array public function onRender(StorefrontRenderEvent $event): void { - $cacheKey = $this->generateCacheKey( - $event->getSalesChannelContext()->getSalesChannel()->getId() + $event->setParameter( + 'activePayonePaymentMethodIds', + $this->activePaymentMethodsLoader->getActivePaymentMethodIds($event->getSalesChannelContext()) ); - - $activePaymentMethods = $this->cachePool->getItem($cacheKey); - - if ($activePaymentMethods->get() === null) { - $activePaymentMethods->set( - $this->collectActivePayonePaymentMethods( - $event->getSalesChannelContext() - ) - ); - - $this->cachePool->save($activePaymentMethods); - } - - $event->setParameter('activePaymentPaymentMethods', $activePaymentMethods->get()); } public function onEntityWritten(EntityWrittenContainerEvent $event): void @@ -86,7 +64,7 @@ public function onEntityWritten(EntityWrittenContainerEvent $event): void } if ($clearCache) { - $this->clearCache($event->getContext()); + $this->activePaymentMethodsLoader->clearCache($event->getContext()); } } @@ -104,37 +82,4 @@ private function collectPrimaryKeys(array $primaryKeys): array return $ids; } - - private function collectActivePayonePaymentMethods(SalesChannelContext $context): array - { - $criteria = new Criteria(); - - $criteria->addFilter(new ContainsFilter('handlerIdentifier', 'PayonePayment')); - $criteria->addFilter(new EqualsFilter('active', true)); - - return $this->paymentMethodRepository->search($criteria, $context)->getIds(); - } - - private function generateCacheKey(string $salesChannel): string - { - return 'payone_payment.menu_state.' . $salesChannel; - } - - private function clearCache(Context $context): void - { - $cacheKeys = []; - - /** @var string[] $salesChannels */ - $salesChannels = $this->salesChannelRepository->searchIds(new Criteria(), $context)->getIds(); - - foreach ($salesChannels as $salesChannel) { - $cacheKeys[] = $this->generateCacheKey($salesChannel); - } - - if (empty($cacheKeys)) { - return; - } - - $this->cachePool->deleteItems($cacheKeys); - } } diff --git a/src/Installer/PaymentMethodInstaller.php b/src/Installer/PaymentMethodInstaller.php index d2cc55fc0..f90e48af7 100644 --- a/src/Installer/PaymentMethodInstaller.php +++ b/src/Installer/PaymentMethodInstaller.php @@ -25,6 +25,8 @@ use PayonePayment\PaymentMethod\PayonePayolutionInvoicing; use PayonePayment\PaymentMethod\PayonePaypal; use PayonePayment\PaymentMethod\PayonePaypalExpress; +use PayonePayment\PaymentMethod\PayonePaypalV2; +use PayonePayment\PaymentMethod\PayonePaypalV2Express; use PayonePayment\PaymentMethod\PayonePostfinanceCard; use PayonePayment\PaymentMethod\PayonePostfinanceWallet; use PayonePayment\PaymentMethod\PayonePrepayment; @@ -62,6 +64,8 @@ class PaymentMethodInstaller implements InstallerInterface PayoneDebit::class => '1b017bef157b4222b734659361d996fd', PayonePaypal::class => '21e157163fdb4aa4862a2109abcd7522', PayonePaypalExpress::class => '5ddf648859a84396a98c97a1a92c107f', + PayonePaypalV2::class => 'ee2195f621eb466d809cace908163017', + PayonePaypalV2Express::class => '57fa8d8c9d3b4e488f5267f624841531', PayonePayolutionInstallment::class => '569b46970ad2458ca8f17f1ebb754137', PayonePayolutionInvoicing::class => '0407fd0a5c4b4d2bafc88379efe8cf8d', PayonePayolutionDebit::class => '700954775fad4a8f92463b3d629c8ad5', @@ -100,6 +104,8 @@ class PaymentMethodInstaller implements InstallerInterface PayoneDebit::class, PayonePaypal::class, PayonePaypalExpress::class, + PayonePaypalV2::class, + PayonePaypalV2Express::class, PayonePayolutionInstallment::class, PayonePayolutionInvoicing::class, PayonePayolutionDebit::class, @@ -135,6 +141,7 @@ class PaymentMethodInstaller implements InstallerInterface PayoneCreditCard::class, PayoneDebit::class, PayonePaypal::class, + PayonePaypalV2::class, PayonePayolutionInstallment::class, PayonePayolutionInvoicing::class, PayonePayolutionDebit::class, diff --git a/src/PaymentHandler/PaymentHandlerGroups.php b/src/PaymentHandler/PaymentHandlerGroups.php index 667e2759d..448ff9bdb 100644 --- a/src/PaymentHandler/PaymentHandlerGroups.php +++ b/src/PaymentHandler/PaymentHandlerGroups.php @@ -6,6 +6,7 @@ use PayonePayment\PaymentMethod\PayoneAmazonPayExpress; use PayonePayment\PaymentMethod\PayonePaypalExpress; +use PayonePayment\PaymentMethod\PayonePaypalV2Express; interface PaymentHandlerGroups { @@ -28,6 +29,7 @@ interface PaymentHandlerGroups public const GENERIC_EXPRESS = [ PayonePaypalExpress::UUID => PayonePaypalExpressPaymentHandler::class, + PayonePaypalV2Express::UUID => PayonePaypalV2ExpressPaymentHandler::class, PayoneAmazonPayExpress::UUID => PayoneAmazonPayExpressPaymentHandler::class, ]; } diff --git a/src/PaymentHandler/PayonePaypalV2ExpressPaymentHandler.php b/src/PaymentHandler/PayonePaypalV2ExpressPaymentHandler.php new file mode 100644 index 000000000..eb90a35a6 --- /dev/null +++ b/src/PaymentHandler/PayonePaypalV2ExpressPaymentHandler.php @@ -0,0 +1,16 @@ +createPaymentException( + $transaction->getOrderTransaction()->getId(), + $this->translator->trans('PayonePayment.errorMessages.genericError') + ); + } + + $data = $this->preparePayoneOrderTransactionData($request, $response); + $this->transactionDataHandler->saveTransactionData($paymentTransaction, $salesChannelContext->getContext(), $data); + } + + protected function getRedirectResponse(SalesChannelContext $context, array $request, array $response): RedirectResponse + { + if (strtolower((string) $response['status']) === 'redirect') { + return new RedirectResponse($response['redirecturl']); + } + + return new RedirectResponse($request['successurl']); + } +} diff --git a/src/PaymentMethod/PayonePaypalV2.php b/src/PaymentMethod/PayonePaypalV2.php new file mode 100644 index 000000000..8a761bab0 --- /dev/null +++ b/src/PaymentMethod/PayonePaypalV2.php @@ -0,0 +1,38 @@ + [ + 'name' => 'PAYONE PayPal v2', + 'description' => 'Zahlen Sie sicher und bequem mit PayPal v2.', + ], + 'en-GB' => [ + 'name' => 'PAYONE PayPal v2', + 'description' => 'Pay easily and secure with PayPal v2.', + ], + ]; + + protected int $position = 102; +} diff --git a/src/PaymentMethod/PayonePaypalV2Express.php b/src/PaymentMethod/PayonePaypalV2Express.php new file mode 100644 index 000000000..6fa783841 --- /dev/null +++ b/src/PaymentMethod/PayonePaypalV2Express.php @@ -0,0 +1,38 @@ + [ + 'name' => 'PAYONE PayPal Express v2', + 'description' => 'Zahlen Sie sicher und bequem mit PayPal Express v2.', + ], + 'en-GB' => [ + 'name' => 'PAYONE PayPal Express v2', + 'description' => 'Pay easily and secure with PayPal Express v2.', + ], + ]; + + protected int $position = 103; +} diff --git a/src/Payone/RequestParameter/Builder/CustomerRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/CustomerRequestParameterBuilder.php index 96bf5af4e..d9287b54a 100644 --- a/src/Payone/RequestParameter/Builder/CustomerRequestParameterBuilder.php +++ b/src/Payone/RequestParameter/Builder/CustomerRequestParameterBuilder.php @@ -21,6 +21,8 @@ use PayonePayment\PaymentHandler\PayonePayolutionInvoicingPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalExpressPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2ExpressPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2PaymentHandler; use PayonePayment\PaymentHandler\PayonePrepaymentPaymentHandler; use PayonePayment\PaymentHandler\PayonePrzelewy24PaymentHandler; use PayonePayment\PaymentHandler\PayoneRatepayDebitPaymentHandler; @@ -127,6 +129,8 @@ public function supports(AbstractRequestParameterStruct $arguments): bool switch ($paymentMethod) { case PayonePaypalPaymentHandler::class: case PayonePaypalExpressPaymentHandler::class: + case PayonePaypalV2PaymentHandler::class: + case PayonePaypalV2ExpressPaymentHandler::class: case PayoneSofortBankingPaymentHandler::class: case PayoneDebitPaymentHandler::class: case PayoneCreditCardPaymentHandler::class: diff --git a/src/Payone/RequestParameter/Builder/PaypalV2/AuthorizeRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2/AuthorizeRequestParameterBuilder.php new file mode 100644 index 000000000..3d8bca598 --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2/AuthorizeRequestParameterBuilder.php @@ -0,0 +1,37 @@ + self::REQUEST_ACTION_AUTHORIZE, + 'clearingtype' => self::CLEARING_TYPE_WALLET, + 'wallettype' => 'PAL', + ]; + } + + public function supports(AbstractRequestParameterStruct $arguments): bool + { + if (!($arguments instanceof PaymentTransactionStruct)) { + return false; + } + + $paymentMethod = $arguments->getPaymentMethod(); + $action = $arguments->getAction(); + + return $paymentMethod === PayonePaypalV2PaymentHandler::class && $action === self::REQUEST_ACTION_AUTHORIZE; + } +} diff --git a/src/Payone/RequestParameter/Builder/PaypalV2/PreAuthorizeRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2/PreAuthorizeRequestParameterBuilder.php new file mode 100644 index 000000000..242f6d736 --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2/PreAuthorizeRequestParameterBuilder.php @@ -0,0 +1,34 @@ + self::REQUEST_ACTION_PREAUTHORIZE, + ]); + } + + public function supports(AbstractRequestParameterStruct $arguments): bool + { + if (!($arguments instanceof PaymentTransactionStruct)) { + return false; + } + + $paymentMethod = $arguments->getPaymentMethod(); + $action = $arguments->getAction(); + + return $paymentMethod === PayonePaypalV2PaymentHandler::class && $action === self::REQUEST_ACTION_PREAUTHORIZE; + } +} diff --git a/src/Payone/RequestParameter/Builder/PaypalV2Express/AuthorizeRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2Express/AuthorizeRequestParameterBuilder.php new file mode 100644 index 000000000..e620ed84b --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2Express/AuthorizeRequestParameterBuilder.php @@ -0,0 +1,25 @@ +getPaymentMethod(); + $action = $arguments->getAction(); + + return $paymentMethod === PayonePaypalV2ExpressPaymentHandler::class && $action === self::REQUEST_ACTION_AUTHORIZE; + } +} diff --git a/src/Payone/RequestParameter/Builder/PaypalV2Express/CreateCheckoutSessionParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2Express/CreateCheckoutSessionParameterBuilder.php new file mode 100644 index 000000000..17316747e --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2Express/CreateCheckoutSessionParameterBuilder.php @@ -0,0 +1,31 @@ + 'setexpresscheckout', + 'clearingtype' => self::CLEARING_TYPE_WALLET, + 'wallettype' => 'PAL', + ]; + } + + public function supports(AbstractRequestParameterStruct $arguments): bool + { + return $arguments instanceof CreateExpressCheckoutSessionStruct + && $arguments->getPaymentMethod() === PayonePaypalV2ExpressPaymentHandler::class; + } +} diff --git a/src/Payone/RequestParameter/Builder/PaypalV2Express/GetCheckoutDetailsRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2Express/GetCheckoutDetailsRequestParameterBuilder.php new file mode 100644 index 000000000..626fdc0db --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2Express/GetCheckoutDetailsRequestParameterBuilder.php @@ -0,0 +1,31 @@ + 'getexpresscheckoutdetails', + 'clearingtype' => self::CLEARING_TYPE_WALLET, + 'wallettype' => 'PAL', + ]; + } + + public function supports(AbstractRequestParameterStruct $arguments): bool + { + return $arguments instanceof GetCheckoutSessionStruct + && $arguments->getPaymentMethod() === PayonePaypalV2ExpressPaymentHandler::class; + } +} diff --git a/src/Payone/RequestParameter/Builder/PaypalV2Express/PreAuthorizeRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/PaypalV2Express/PreAuthorizeRequestParameterBuilder.php new file mode 100644 index 000000000..7e06e6a5b --- /dev/null +++ b/src/Payone/RequestParameter/Builder/PaypalV2Express/PreAuthorizeRequestParameterBuilder.php @@ -0,0 +1,34 @@ + self::REQUEST_ACTION_PREAUTHORIZE, + ]); + } + + public function supports(AbstractRequestParameterStruct $arguments): bool + { + if (!($arguments instanceof PaymentTransactionStruct)) { + return false; + } + + $paymentMethod = $arguments->getPaymentMethod(); + $action = $arguments->getAction(); + + return $paymentMethod === PayonePaypalV2ExpressPaymentHandler::class && $action === self::REQUEST_ACTION_PREAUTHORIZE; + } +} diff --git a/src/Payone/RequestParameter/Builder/ReturnUrlRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/ReturnUrlRequestParameterBuilder.php index 0bf988eda..7aaa7ae22 100644 --- a/src/Payone/RequestParameter/Builder/ReturnUrlRequestParameterBuilder.php +++ b/src/Payone/RequestParameter/Builder/ReturnUrlRequestParameterBuilder.php @@ -17,6 +17,8 @@ use PayonePayment\PaymentHandler\PayonePaydirektPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalExpressPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2ExpressPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2PaymentHandler; use PayonePayment\PaymentHandler\PayonePrzelewy24PaymentHandler; use PayonePayment\PaymentHandler\PayoneSofortBankingPaymentHandler; use PayonePayment\PaymentHandler\PayoneTrustlyPaymentHandler; @@ -58,6 +60,8 @@ public function supports(AbstractRequestParameterStruct $arguments): bool switch ($paymentMethod) { case PayonePaypalPaymentHandler::class: case PayonePaypalExpressPaymentHandler::class: + case PayonePaypalV2PaymentHandler::class: + case PayonePaypalV2ExpressPaymentHandler::class: case PayoneSofortBankingPaymentHandler::class: case PayoneCreditCardPaymentHandler::class: case PayoneTrustlyPaymentHandler::class: diff --git a/src/Payone/RequestParameter/Builder/ShippingInformationRequestParameterBuilder.php b/src/Payone/RequestParameter/Builder/ShippingInformationRequestParameterBuilder.php index e7086ec1a..566eee443 100644 --- a/src/Payone/RequestParameter/Builder/ShippingInformationRequestParameterBuilder.php +++ b/src/Payone/RequestParameter/Builder/ShippingInformationRequestParameterBuilder.php @@ -12,6 +12,8 @@ use PayonePayment\PaymentHandler\PayonePayolutionInvoicingPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalExpressPaymentHandler; use PayonePayment\PaymentHandler\PayonePaypalPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2ExpressPaymentHandler; +use PayonePayment\PaymentHandler\PayonePaypalV2PaymentHandler; use PayonePayment\PaymentHandler\PayoneSecuredDirectDebitPaymentHandler; use PayonePayment\PaymentHandler\PayoneSecuredInstallmentPaymentHandler; use PayonePayment\PaymentHandler\PayoneSecuredInvoicePaymentHandler; @@ -59,6 +61,8 @@ public function supports(AbstractRequestParameterStruct $arguments): bool PayonePaydirektPaymentHandler::class, PayonePaypalPaymentHandler::class, PayonePaypalExpressPaymentHandler::class, + PayonePaypalV2PaymentHandler::class, + PayonePaypalV2ExpressPaymentHandler::class, PayonePayolutionInvoicingPaymentHandler::class, PayonePayolutionDebitPaymentHandler::class, PayonePayolutionInstallmentPaymentHandler::class, diff --git a/src/Resources/app/administration/src/module/payone-payment/snippet/de_DE.json b/src/Resources/app/administration/src/module/payone-payment/snippet/de_DE.json index 6eb2b5e39..2328d70a5 100644 --- a/src/Resources/app/administration/src/module/payone-payment/snippet/de_DE.json +++ b/src/Resources/app/administration/src/module/payone-payment/snippet/de_DE.json @@ -45,6 +45,8 @@ "debit": "Die API-Zugangsdaten für PAYONE Lastschrift sind nicht korrekt.", "paypalExpress": "Die API-Zugangsdaten für PAYONE PayPal Express sind nicht korrekt.", "paypal": "Die API-Zugangsdaten für PAYONE PayPal sind nicht korrekt.", + "paypalV2Express": "Die API-Zugangsdaten für PAYONE PayPal Express v2 sind nicht korrekt.", + "paypalV2": "Die API-Zugangsdaten für PAYONE PayPal v2 sind nicht korrekt.", "payolutionInstallment": "Die API-Zugangsdaten für PAYONE Unzer Ratenkauf sind nicht korrekt.", "payolutionInvoicing": "Die API-Zugangsdaten für PAYONE Unzer Rechnungskauf sind nicht korrekt.", "payolutionDebit": "Die API-Zugangsdaten für PAYONE Unzer Lastschrift sind nicht korrekt.", diff --git a/src/Resources/app/administration/src/module/payone-payment/snippet/en_GB.json b/src/Resources/app/administration/src/module/payone-payment/snippet/en_GB.json index c2ecd25de..c726fed85 100644 --- a/src/Resources/app/administration/src/module/payone-payment/snippet/en_GB.json +++ b/src/Resources/app/administration/src/module/payone-payment/snippet/en_GB.json @@ -45,6 +45,8 @@ "debit": "The API credentials for PAYONE Direct Debit are not valid.", "paypalExpress": "The API credentials for PAYONE PayPal Express are not valid.", "paypal": "The API credentials for PAYONE PayPal are not valid.", + "paypalV2Express": "The API credentials for PAYONE PayPal Express v2 are not valid.", + "paypalV2": "The API credentials for PAYONE PayPal v2 are not valid.", "payolutionInstallment": "The API credentials for PAYONE Unzer Ratenkauf are not valid.", "payolutionInvoicing": "The API credentials for PAYONE Unzer Rechnungskauf are not valid.", "payolutionDebit": "The API credentials for PAYONE Unzer Lastschrift are not valid.", diff --git a/src/Resources/app/storefront/dist/storefront/js/payone-payment/payone-payment.js b/src/Resources/app/storefront/dist/storefront/js/payone-payment/payone-payment.js index ca5c5abe6..bd29a01c0 100644 --- a/src/Resources/app/storefront/dist/storefront/js/payone-payment/payone-payment.js +++ b/src/Resources/app/storefront/dist/storefront/js/payone-payment/payone-payment.js @@ -1 +1 @@ -(()=>{"use strict";var e={857:e=>{var t=function(e){var t;return!!e&&"object"==typeof e&&"[object RegExp]"!==(t=Object.prototype.toString.call(e))&&"[object Date]"!==t&&e.$$typeof!==n},n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?s(Array.isArray(e)?[]:{},e,t):e}function a(e,t,n){return e.concat(t).map(function(e){return r(e,n)})}function i(e){return Object.keys(e).concat(Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return Object.propertyIsEnumerable.call(e,t)}):[])}function o(e,t){try{return t in e}catch(e){return!1}}function s(e,n,l){(l=l||{}).arrayMerge=l.arrayMerge||a,l.isMergeableObject=l.isMergeableObject||t,l.cloneUnlessOtherwiseSpecified=r;var d,c,u=Array.isArray(n);return u!==Array.isArray(e)?r(n,l):u?l.arrayMerge(e,n,l):(c={},(d=l).isMergeableObject(e)&&i(e).forEach(function(t){c[t]=r(e[t],d)}),i(n).forEach(function(t){(!o(e,t)||Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))&&(o(e,t)&&d.isMergeableObject(n[t])?c[t]=(function(e,t){if(!t.customMerge)return s;var n=t.customMerge(e);return"function"==typeof n?n:s})(t,d)(e[t],n[t],d):c[t]=r(n[t],d))}),c)}s.all=function(e,t){if(!Array.isArray(e))throw Error("first argument should be an array");return e.reduce(function(e,n){return s(e,n,t)},{})},e.exports=s}},t={};function n(r){var a=t[r];if(void 0!==a)return a.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}(()=>{n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t}})(),(()=>{n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})}})(),(()=>{n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{var e=n(857),t=n.n(e);class r{static ucFirst(e){return e.charAt(0).toUpperCase()+e.slice(1)}static lcFirst(e){return e.charAt(0).toLowerCase()+e.slice(1)}static toDashCase(e){return e.replace(/([A-Z])/g,"-$1").replace(/^-/,"").toLowerCase()}static toLowerCamelCase(e,t){let n=r.toUpperCamelCase(e,t);return r.lcFirst(n)}static toUpperCamelCase(e,t){return t?e.split(t).map(e=>r.ucFirst(e.toLowerCase())).join(""):r.ucFirst(e.toLowerCase())}static parsePrimitive(e){try{return/^\d+(.|,)\d+$/.test(e)&&(e=e.replace(",",".")),JSON.parse(e)}catch(t){return e.toString()}}}class a{static isNode(e){return"object"==typeof e&&null!==e&&(e===document||e===window||e instanceof Node)}static hasAttribute(e,t){if(!a.isNode(e))throw Error("The element must be a valid HTML Node!");return"function"==typeof e.hasAttribute&&e.hasAttribute(t)}static getAttribute(e,t){let n=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(n&&!1===a.hasAttribute(e,t))throw Error('The required property "'.concat(t,'" does not exist!'));if("function"!=typeof e.getAttribute){if(n)throw Error("This node doesn't support the getAttribute function!");return}return e.getAttribute(t)}static getDataAttribute(e,t){let n=!(arguments.length>2)||void 0===arguments[2]||arguments[2],i=t.replace(/^data(|-)/,""),o=r.toLowerCamelCase(i,"-");if(!a.isNode(e)){if(n)throw Error("The passed node is not a valid HTML Node!");return}if(void 0===e.dataset){if(n)throw Error("This node doesn't support the dataset attribute!");return}let s=e.dataset[o];if(void 0===s){if(n)throw Error('The required data attribute "'.concat(t,'" does not exist on ').concat(e,"!"));return s}return r.parsePrimitive(s)}static querySelector(e,t){let n=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(n&&!a.isNode(e))throw Error("The parent node is not a valid HTML Node!");let r=e.querySelector(t)||!1;if(n&&!1===r)throw Error('The required element "'.concat(t,'" does not exist in parent node!'));return r}static querySelectorAll(e,t){let n=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(n&&!a.isNode(e))throw Error("The parent node is not a valid HTML Node!");let r=e.querySelectorAll(t);if(0===r.length&&(r=!1),n&&!1===r)throw Error('At least one item of "'.concat(t,'" must exist in parent node!'));return r}}class i{publish(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=new CustomEvent(e,{detail:t,cancelable:n});return this.el.dispatchEvent(r),r}subscribe(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=this,a=e.split("."),i=n.scope?t.bind(n.scope):t;if(n.once&&!0===n.once){let t=i;i=function(n){r.unsubscribe(e),t(n)}}return this.el.addEventListener(a[0],i),this.listeners.push({splitEventName:a,opts:n,cb:i}),!0}unsubscribe(e){let t=e.split(".");return this.listeners=this.listeners.reduce((e,n)=>([...n.splitEventName].sort().toString()===t.sort().toString()?this.el.removeEventListener(n.splitEventName[0],n.cb):e.push(n),e),[]),!0}reset(){return this.listeners.forEach(e=>{this.el.removeEventListener(e.splitEventName[0],e.cb)}),this.listeners=[],!0}get el(){return this._el}set el(e){this._el=e}get listeners(){return this._listeners}set listeners(e){this._listeners=e}constructor(e=document){this._el=e,e.$emitter=this,this._listeners=[]}}class o{init(){throw Error('The "init" method for the plugin "'.concat(this._pluginName,'" is not defined.'))}update(){}_init(){this._initialized||(this.init(),this._initialized=!0)}_update(){this._initialized&&this.update()}_mergeOptions(e){let n=r.toDashCase(this._pluginName),i=a.getDataAttribute(this.el,"data-".concat(n,"-config"),!1),o=a.getAttribute(this.el,"data-".concat(n,"-options"),!1),s=[this.constructor.options,this.options,e];i&&s.push(window.PluginConfigManager.get(this._pluginName,i));try{o&&s.push(JSON.parse(o))}catch(e){throw console.error(this.el),Error('The data attribute "data-'.concat(n,'-options" could not be parsed to json: ').concat(e.message))}return t().all(s.filter(e=>e instanceof Object&&!(e instanceof Array)).map(e=>e||{}))}_registerInstance(){window.PluginManager.getPluginInstancesFromElement(this.el).set(this._pluginName,this),window.PluginManager.getPlugin(this._pluginName,!1).get("instances").push(this)}_getPluginName(e){return e||(e=this.constructor.name),e}constructor(e,t={},n=!1){if(!a.isNode(e))throw Error("There is no valid element given.");this.el=e,this.$emitter=new i(this.el),this._pluginName=this._getPluginName(n),this.options=this._mergeOptions(t),this._initialized=!1,this._registerInstance(),this._init()}}class s{static iterate(e,t){if(e instanceof Map||Array.isArray(e))return e.forEach(t);if(e instanceof FormData){for(var n of e.entries())t(n[1],n[0]);return}if(e instanceof NodeList)return e.forEach(t);if(e instanceof HTMLCollection)return Array.from(e).forEach(t);if(e instanceof Object)return Object.keys(e).forEach(n=>{t(e[n],n)});throw Error("The element type ".concat(typeof e," is not iterable!"))}}let l="loader",d={BEFORE:"before",INNER:"inner"};class c{create(){if(!this.exists()){if(this.position===d.INNER){this.parent.innerHTML=c.getTemplate();return}this.parent.insertAdjacentHTML(this._getPosition(),c.getTemplate())}}remove(){let e=this.parent.querySelectorAll(".".concat(l));s.iterate(e,e=>e.remove())}exists(){return this.parent.querySelectorAll(".".concat(l)).length>0}_getPosition(){return this.position===d.BEFORE?"afterbegin":"beforeend"}static getTemplate(){return'
\n Loading...\n
')}static SELECTOR_CLASS(){return l}constructor(e,t=d.BEFORE){this.parent=e instanceof Element?e:document.body.querySelector(e),this.position=t}}class u extends c{create(){super.create(),this.parent.disabled=!0}remove(){super.remove(),this.parent.disabled=!1}_isButtonElement(){return"button"===this.parent.tagName.toLowerCase()}constructor(e,t="before"){if(super(e,t),!1===this._isButtonElement())throw Error("Parent element is not of type