diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index a1d103ce..00000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @acampos1916 @peterojo diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 781e0b5f..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,31 +0,0 @@ -on: ["push", "pull_request"] -name: Main Workflow - -jobs: - run: - name: Run - runs-on: ubuntu-latest - - steps: - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - - - name: Checkout - uses: actions/checkout@v2 - - - name: Make sure we are using composer v1 - run: sudo composer self-update --1 && sudo chown $USER $HOME/.composer - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run PHP Code Sniffer - run: vendor/bin/phpcs . - - - name: Make sure project files are compilable - run: find -L . -path ./vendor -prune -o -path ./tests -prune -o -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l diff --git a/.gitignore b/.gitignore index db2e5096..9e59d36b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,53 @@ -#development +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE .idea/ -*.iml + +# Unit test reports +TEST*.xml + +# Generated by MacOS .DS_Store -#composer +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + vendor/ -composer.lock - -# project -.php-cs-fixer.php -psalm.xml -grumphp.yml -.*.cache -tools/vendor + tools/AdyenPayment.zip -!tools/composer.lock -tests/fixtures/applepay/* -!tests/fixtures/applepay/ZipExtractor.zip \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php deleted file mode 100644 index bd0abb31..00000000 --- a/.php-cs-fixer.dist.php +++ /dev/null @@ -1,93 +0,0 @@ -setFinder( - PhpCsFixer\Finder::create() - ->in([__DIR__]) - ->exclude([ - 'vendor', - 'Controllers' // Contains SW5 specific code - ]) - ->notPath([ - '/Models\/(Notification|PaymentInfo|Refund|TextNotification)\.php/', // TODO: update doctrine entity mapping - '/Components/Builder/NotificationBuilder.php', // TODO: update doctrine entity mapping - ]) - ->name('*.php')) - ->setRiskyAllowed(true) - ->setRules([ - '@PSR12' => true, - '@Symfony' => true, - 'align_multiline_comment' => true, - 'array_indentation' => true, - 'array_syntax' => ['syntax' => 'short'], - 'backtick_to_shell_exec' => true, - 'blank_line_before_statement' => true, - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - 'comment_to_phpdoc' => true, - 'phpdoc_to_comment' => false, - 'compact_nullable_typehint' => true, -// 'date_time_immutable' => true, // TODO: update doctrine entity mapping - 'declare_strict_types' => true, - 'doctrine_annotation_array_assignment' => true, - 'doctrine_annotation_braces' => true, - 'doctrine_annotation_indentation' => true, - 'doctrine_annotation_spaces' => true, - 'escape_implicit_backslashes' => true, - 'explicit_indirect_variable' => true, - 'explicit_string_variable' => true, - 'final_internal_class' => false, - 'fully_qualified_strict_types' => true, - 'general_phpdoc_annotation_remove' => false, - 'header_comment' => false, - 'heredoc_to_nowdoc' => false, - 'linebreak_after_opening_tag' => true, - 'list_syntax' => ['syntax' => 'short'], - 'mb_str_functions' => true, - 'method_chaining_indentation' => true, - 'multiline_comment_opening_closing' => true, - 'multiline_whitespace_before_semicolons' => true, - 'native_function_invocation' => false, - 'no_alternative_syntax' => true, - 'no_blank_lines_before_namespace' => false, - 'no_null_property_initialization' => true, - 'no_php4_constructor' => true, - 'echo_tag_syntax' => true, - 'no_superfluous_elseif' => true, - 'no_unreachable_default_argument_value' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'not_operator_with_space' => false, - 'not_operator_with_successor_space' => false, - 'ordered_class_elements' => false, - 'ordered_imports' => true, - 'php_unit_dedicate_assert' => false, - 'php_unit_expectation' => false, - 'php_unit_mock' => false, - 'php_unit_namespaced' => false, - 'php_unit_no_expectation_annotation' => false, - 'phpdoc_order_by_value' => true, - 'php_unit_set_up_tear_down_visibility' => true, - 'php_unit_strict' => false, - 'php_unit_test_annotation' => false, - 'php_unit_test_class_requires_covers' => false, - 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_order' => true, - 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], - 'php_unit_method_casing' => ['case' =>'snake_case'], - 'pow_to_exponentiation' => true, - 'psr_autoloading' => true, - 'random_api_migration' => false, - 'simplified_null_return' => true, - 'static_lambda' => true, // can collide with Prohpecy - 'strict_comparison' => true, - 'strict_param' => true, - 'string_line_ending' => true, - 'ternary_to_null_coalescing' => true, - 'void_return' => true, - 'yoda_style' => true, - 'single_line_throw' => false, - 'class_attributes_separation' => ['elements' =>['property' => 'only_if_meta', 'const' => 'only_if_meta']], - 'function_declaration' => ['closure_function_spacing' => 'none'], - ]); diff --git a/AdyenApi/HttpClient/ClientFactory.php b/AdyenApi/HttpClient/ClientFactory.php deleted file mode 100755 index add3464d..00000000 --- a/AdyenApi/HttpClient/ClientFactory.php +++ /dev/null @@ -1,57 +0,0 @@ -configuration = $configuration; - $this->logger = $logger; - } - - /** - * @throws AdyenException - */ - public function provide(Shop $shop): Client - { - return $this->createClient( - $this->configuration->getMerchantAccount($shop), - $this->configuration->getApiKey($shop), - $this->configuration->getEnvironment($shop), - $this->configuration->getApiUrlPrefix($shop) - ); - } - - private function createClient( - string $merchantAccount, - string $apiKey, - string $environment, - ?string $prefix = null - ): Client { - $urlPrefix = Environment::LIVE === $environment ? $prefix : null; - - $adyenClient = new Client(); - $adyenClient->setMerchantAccount($merchantAccount); - $adyenClient->setXApiKey($apiKey); - $adyenClient->setEnvironment($environment, $urlPrefix); - $adyenClient->setLogger($this->logger); - - return $adyenClient; - } -} diff --git a/AdyenApi/HttpClient/ClientFactoryInterface.php b/AdyenApi/HttpClient/ClientFactoryInterface.php deleted file mode 100644 index 6cd6fb44..00000000 --- a/AdyenApi/HttpClient/ClientFactoryInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - */ - private $memoisedClients = []; - - /** @var ClientFactoryInterface */ - private $factory; - - public function __construct(ClientFactoryInterface $factory) - { - $this->factory = $factory; - } - - public function lookup(Shop $shop): Client - { - if (!array_key_exists($shop->getId(), $this->memoisedClients)) { - $this->memoisedClients[$shop->getId()] = $this->factory->provide($shop); - } - - return $this->memoisedClients[$shop->getId()]; - } -} diff --git a/AdyenApi/HttpClient/ClientMemoiseInterface.php b/AdyenApi/HttpClient/ClientMemoiseInterface.php deleted file mode 100644 index da6263eb..00000000 --- a/AdyenApi/HttpClient/ClientMemoiseInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -adyenApiFactory = $adyenApiFactory; - $this->configuration = $configuration; - $this->shopRepository = $shopRepository; - } - - public function validate(int $shopId): ConstraintViolationList - { - $shop = $this->shopRepository->find($shopId); - if (null === $shop) { - return new ConstraintViolationList([ - ConstraintViolationFactory::create('Shop not found for ID "'.$shopId.'".'), - ]); - } - - $violations = $this->validateConfig($shop); - if ($violations->count()) { - return $violations; - } - - return $this->validateConnection($shop); - } - - private function validateConfig(Shop $shop): ConstraintViolationList - { - $violations = new ConstraintViolationList(); - $shopApiKey = $this->configuration->getApiKey($shop); - $shopMerchantAccount = $this->configuration->getMerchantAccount($shop); - if ('' === $shopApiKey) { - $violations->add(ConstraintViolationFactory::create('Missing configuration: API key.')); - } - - if ('' === $shopMerchantAccount) { - $violations->add(ConstraintViolationFactory::create('Missing configuration: merchant account.')); - } - - return $violations; - } - - private function validateConnection(Shop $shop): ConstraintViolationList - { - try { - $adyenClient = $this->adyenApiFactory->provide($shop); - $checkout = new Checkout($adyenClient); - $checkout->paymentMethods([ - 'merchantAccount' => $this->configuration->getMerchantAccount($shop), - ]); - } catch (AdyenException $exception) { - return new ConstraintViolationList([ - ConstraintViolationFactory::create('Adyen API failed, check error logs'), - ]); - } - - return new ConstraintViolationList(); - } -} diff --git a/AdyenApi/HttpClient/ConfigValidatorInterface.php b/AdyenApi/HttpClient/ConfigValidatorInterface.php deleted file mode 100644 index 610542b9..00000000 --- a/AdyenApi/HttpClient/ConfigValidatorInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -success = $success; - $this->message = $message; - } - - public static function create(bool $success, string $message): self - { - return new self($success, $message); - } - - public static function empty(): self - { - return new self(false, 'Customer number not found.'); - } - - public function isSuccess(): bool - { - return $this->success; - } - - public function message(): string - { - return $this->message; - } -} diff --git a/AdyenApi/Recurring/DisableTokenRequestHandler.php b/AdyenApi/Recurring/DisableTokenRequestHandler.php deleted file mode 100755 index a9fd2c67..00000000 --- a/AdyenApi/Recurring/DisableTokenRequestHandler.php +++ /dev/null @@ -1,58 +0,0 @@ -transportFactory = $transportFactory; - $this->customerNumberProvider = $customerNumberProvider; - } - - public function disableToken(string $recurringTokenId, Shop $shop): ApiResponse - { - $customerNumber = ($this->customerNumberProvider)(); - if ('' === $customerNumber) { - return ApiResponse::empty(); - } - $recurringTransport = $this->transportFactory->recurring($shop); - - $payload = [ - 'shopperReference' => $customerNumber, - 'recurringDetailReference' => $recurringTokenId, - ]; - - $result = $recurringTransport->disable($payload); - - $response = (string) ($result['response'] ?? ''); - $resultMessage = (string) ($result['message'] ?? ''); - $isSuccessfullyDisabled = $this->isSuccessfullyDisabled($response); - - return ApiResponse::create($isSuccessfullyDisabled, $resultMessage); - } - - private function isSuccessfullyDisabled(string $response): bool - { - if (false === mb_strpos($response, 'successfully-disabled')) { - return false; - } - - return true; - } -} diff --git a/AdyenApi/Recurring/DisableTokenRequestHandlerInterface.php b/AdyenApi/Recurring/DisableTokenRequestHandlerInterface.php deleted file mode 100644 index 27317f1e..00000000 --- a/AdyenApi/Recurring/DisableTokenRequestHandlerInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -apiFactory = $apiFactory; - } - - public function recurring(Shop $shop): Recurring - { - return new Recurring( - $this->apiFactory->provide($shop) - ); - } - - public function checkout(Shop $shop): Checkout - { - return new Checkout( - $this->apiFactory->provide($shop) - ); - } -} diff --git a/AdyenApi/TransportFactoryInterface.php b/AdyenApi/TransportFactoryInterface.php deleted file mode 100644 index e79d876c..00000000 --- a/AdyenApi/TransportFactoryInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -addCompilerPass(new NotificationProcessorCompilerPass()); - parent::build($container); + $container->setParameter('adyen_payment.plugin_dir', $this->getPath()); - //set default logger level for 5.4 - if (!$container->hasParameter('kernel.default_error_level')) { - $container->setParameter('kernel.default_error_level', Logger::ERROR); - } + parent::build($container); $this->loadServices($container); } - private function loadServices(ContainerBuilder $container): void - { - $loader = new GlobFileLoader($container, $fileLocator = new FileLocator()); - $loader->setResolver(new LoaderResolver([new XmlFileLoader($container, $fileLocator)])); - - $loader->load(__DIR__.'/Resources/services/*.xml'); - - /** @var ShopwareVersionCheck $versionCheck */ - $versionCheck = $container->get('adyen_payment.components.shopware_version_check'); - if ($versionCheck && $versionCheck->isHigherThanShopwareVersion('v5.6.2')) { - $loader->load(__DIR__.'/Resources/services/version/563/*.xml'); - } - } - /** - * @throws \Exception + * Adds the widget to the database and creates the database schema. + * + * @param Plugin\Context\InstallContext $installContext */ - public function install(InstallContext $context): void + public function install(Plugin\Context\InstallContext $installContext) { - $this->installAttributes(); - $this->installStoredPaymentUmbrella($context); + parent::install($installContext); - $tool = new SchemaTool($this->container->get('models')); - $classes = $this->getModelMetaData(); - $tool->updateSchema($classes, true); + $this->container->get('shopware.snippet_database_handler')->loadToDatabase( + $this->getPath() . '/Resources/snippets/' + ); - $context->scheduleClearCache(InstallContext::CACHE_LIST_FRONTEND); + $this->updateSchema(); + $this->installStoredPaymentUmbrella(); + + $installContext->scheduleClearCache(InstallContext::CACHE_LIST_FRONTEND); } public function update(UpdateContext $context): void { - $this->installAttributes(); - $this->installStoredPaymentUmbrella($context); + Bootstrap::init(); - if (version_compare($context->getCurrentVersion(), '3.9.7', '<=')) { - $this->activatePaymentsForEsd(); - } + $this->container->get('shopware.snippet_database_handler')->loadToDatabase( + $this->getPath() . '/Resources/snippets/' + ); - $tool = new SchemaTool($this->container->get('models')); - $classes = $this->getModelMetaData(); - $tool->updateSchema($classes, true); + $this->updateSchema(); + + $updater = new Updater( + $context, + $this->container->get('shopware.plugin.cached_config_reader'), + ServiceRegister::getService(ConnectionService::class), + $this->container->get(StoreRepository::class), + ServiceRegister::getService(PaymentMethodConfigRepository::class), + $this->container->get('snippets'), + $this->container->get('cron'), + ServiceRegister::getService(QueueService::class), + ServiceRegister::getService(ConnectionSettingsRepository::class) + ); + $updater->update(); + + $this->installStoredPaymentUmbrella(); $context->scheduleClearCache(InstallContext::CACHE_LIST_FRONTEND); + $this->migrateLegacySchema(); parent::update($context); } + public function deactivate(DeactivateContext $context): void + { + /** @var PaymentMethodService $paymentMethodService */ + $paymentMethodService = ServiceRegister::getService(ShopPaymentService::class); + $paymentMethodService->deletePaymentMethodsForAllStores(); + $this->installStoredPaymentUmbrella(false); + + $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + } + + public function activate(ActivateContext $context): void + { + $this->initServices(); + /** @var PaymentMethodService $paymentMethodService */ + $paymentMethodService = ServiceRegister::getService(ShopPaymentService::class); + $paymentMethodService->enableAllPaymentMethods(); + $this->installStoredPaymentUmbrella(); + + $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + } + /** - * @throws \Exception + * Remove widget and remove database schema. + * + * @param Plugin\Context\UninstallContext $uninstallContext + * + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface */ - public function uninstall(UninstallContext $context): void + public function uninstall(Plugin\Context\UninstallContext $uninstallContext) { - if (!$context->keepUserData()) { - $this->uninstallAttributes($context); + parent::uninstall($uninstallContext); + $this->initServices(); + + if ($uninstallContext->keepUserData()) { + /** @var PaymentMethodService $paymentService */ + $paymentService = ServiceRegister::getService(ShopPaymentService::class); + $paymentService->deletePaymentMethodsForAllStores(); + $this->installStoredPaymentUmbrella(false); + + return; + } - $tool = new SchemaTool($this->container->get('models')); - $classes = $this->getModelMetaData(); - $tool->dropSchema($classes); + $uninstallService = new UninstallService( + new StoreService( + new StoreRepository(Shopware()->Models()->getRepository(Shop::class)), + new OrderRepository(Shopware()->Models()->getRepository(Order::class)), + RepositoryRegistry::getRepository(ConnectionSettings::getClassName()) + ) + ); + try { + $uninstallService->uninstall(); + } catch (Exception $exception) { + $this->container->get('corelogger')->warning($exception->getMessage()); } - if ($context->getPlugin()->getActive()) { - $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + $this->installStoredPaymentUmbrella(false); + + $this->removeSchema(); + } + + private function loadServices(ContainerBuilder $container): void + { + $loader = new GlobFileLoader($container, $fileLocator = new FileLocator()); + $loader->setResolver(new LoaderResolver([new XmlFileLoader($container, $fileLocator)])); + + $loader->load(__DIR__ . '/Resources/services/*.xml'); + + $versionCheck = $container->get('adyen_payment.components.shopware_version_check'); + if ($versionCheck && $versionCheck->isHigherThanShopwareVersion('v5.6.2')) { + $loader->load(__DIR__ . '/Resources/services/version/563/*.xml'); } } - public function deactivate(DeactivateContext $context): void + /** + * Creates/updates database tables on base of doctrine models + */ + private function updateSchema() { - $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + $tool = new SchemaTool($this->container->get('models')); + + $tool->updateSchema($this->getModelMetaData(), true); } - public function activate(ActivateContext $context): void + private function removeSchema(): void { - $this->installStoredPaymentUmbrella($context); - $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + $tool = new SchemaTool($this->container->get('models')); + + $tool->dropSchema($this->getModelMetaData()); + $this->removeLegacySchema(); } - /** - * @throws \Exception - */ - private function uninstallAttributes(UninstallContext $uninstallContext): void + private function removeLegacySchema(): void { - $crudService = $this->container->get('shopware_attribute.crud_service'); - $crudService->delete('s_core_paymentmeans_attributes', self::ADYEN_CODE); + $sql = 'DROP TABLE IF EXISTS `s_plugin_adyen_order_notification`; + DROP TABLE IF EXISTS `s_plugin_adyen_order_payment_info`; + DROP TABLE IF EXISTS `s_plugin_adyen_order_refund`; + DROP TABLE IF EXISTS `s_plugin_adyen_text_notification`; + DROP TABLE IF EXISTS `s_plugin_adyen_payment_recurring_payment_token`;'; - $this->rebuildAttributeModels(); + $this->container->get('dbal_connection')->exec($sql); } - /** - * @throws \Exception - */ - private function installAttributes(): void + private function migrateLegacySchema() { - $crudService = $this->container->get('shopware_attribute.crud_service'); - $crudService->update( - 's_core_paymentmeans_attributes', - self::ADYEN_CODE, - TypeMapping::TYPE_STRING, - [ - 'displayInBackend' => true, - 'readonly' => true, - 'label' => 'Adyen payment type', - ] - ); + $sql = 'DROP TABLE IF EXISTS `s_plugin_adyen_order_payment_info`; + DROP TABLE IF EXISTS `s_plugin_adyen_order_refund`; + DROP TABLE IF EXISTS `s_plugin_adyen_payment_recurring_payment_token`;'; - $this->rebuildAttributeModels(); + $this->container->get('dbal_connection')->exec($sql); } private function getModelMetaData(): array @@ -179,75 +244,71 @@ private function getModelMetaData(): array $entityManager = $this->container->get('models'); return [ - $entityManager->getClassMetadata(Notification::class), - $entityManager->getClassMetadata(PaymentInfo::class), - $entityManager->getClassMetadata(Refund::class), - $entityManager->getClassMetadata(TextNotification::class), + $entityManager->getClassMetadata(AdyenEntity::class), + $entityManager->getClassMetadata(NotificationsEntity::class), + $entityManager->getClassMetadata(QueueEntity::class), + $entityManager->getClassMetadata(TransactionLogEntity::class), $entityManager->getClassMetadata(UserPreference::class), - $entityManager->getClassMetadata(RecurringPaymentToken::class), ]; } - private function rebuildAttributeModels(): void + private function installStoredPaymentUmbrella($isActive = true): void { - $metaDataCache = $this->container->get('models')->getConfiguration()->getMetadataCacheImpl(); - if ($metaDataCache) { - $metaDataCache->deleteAll(); - } - - $this->container->get('models')->generateAttributeModels( - ['s_user_attributes', 's_core_paymentmeans_attributes'] + /** @var PaymentInstaller $installer */ + $installer = $this->container->get('shopware.plugin_payment_installer'); + $installer->createOrUpdate( + self::NAME, + [ + 'name' => self::STORED_PAYMENT_UMBRELLA_NAME, + 'description' => 'Adyen Stored Payment Method', + 'additionalDescription' => 'Adyen Stored Payment Method', + 'active' => $isActive, + 'esdActive' => $isActive, + 'hide' => true, + 'action' => 'AdyenPaymentProcess', + 'source' => self::PAYMENT_METHOD_SOURCE, + ] ); } - private function installStoredPaymentUmbrella(InstallContext $context): void + private function initServices(): void { - $database = $this->container->get('db'); - /** @var ModelManager $modelsManager */ - $modelsManager = $this->container->get('shopware.model_manager'); - - $models = $this->container->get('models'); - $shops = $models->getRepository(Shop::class)->findAll(); - - $payment = new Payment(); - $payment->setActive(true); - $payment->setEsdActive(true); - $payment->setName(self::ADYEN_STORED_PAYMENT_UMBRELLA_CODE); - $payment->setSource(SourceType::adyen()->getType()); - $payment->setHide(true); - $payment->setPluginId($context->getPlugin()->getId()); - $payment->setDescription($description = 'Adyen Stored Payment Method'); - $payment->setAdditionalDescription($description); - $payment->setShops($shops); - - $paymentInDb = $database->fetchRow( - 'SELECT `id`, `active` FROM `s_core_paymentmeans` WHERE `name` = :name', - [':name' => self::ADYEN_STORED_PAYMENT_UMBRELLA_CODE] + Bootstrap::init(); + + ServiceRegister::registerService( + ShopPaymentService::class, + static function () { + return new PaymentMethodService( + ServiceRegister::getService(StoreContext::class), + Shopware()->Models(), + new StoreRepository(Shopware()->Models()->getRepository(Shop::class)), + Shopware()->Container()->get('shopware.plugin_payment_installer'), + ServiceRegister::getService(FileService::class), + ServiceRegister::getService(PaymentMethodConfigRepository::class), + ServiceRegister::getService(StoreServiceInterface::class) + ); + } ); - $paymentId = $paymentInDb['id'] ?? null; - - if (null === $paymentId) { - $modelsManager->persist($payment); - $modelsManager->flush($payment); - } - - if (null !== $paymentId && !$paymentInDb['active']) { - $database->update( - 's_core_paymentmeans', - ['active' => true, 'esdactive' => true], - ['id = ?' => $paymentId] - ); - } - } + ServiceRegister::registerService( + StoreServiceInterface::class, + static function () { + return new StoreService( + new StoreRepository(Shopware()->Models()->getRepository(Shop::class)), + new OrderRepository(Shopware()->Models()->getRepository(Order::class)), + RepositoryRegistry::getRepository(ConnectionSettings::getClassName()) + ); + } + ); - private function activatePaymentsForEsd(): void - { - $database = $this->container->get('db'); - $database->update( - 's_core_paymentmeans', - ['esdactive' => true], - ['source = ?' => SourceType::adyen()->getType()] + ServiceRegister::registerService( + OrderServiceInterface::class, + static function () { + return new OrderService( + new OrderRepository(Shopware()->Models()->getRepository(Order::class)), + Shopware()->Modules() + ); + } ); } } @@ -255,4 +316,3 @@ private function activatePaymentsForEsd(): void if (AdyenPayment::isPackage()) { require_once AdyenPayment::getPackageVendorAutoload(); } -//phpcs:enable PSR1.Files.SideEffects diff --git a/Applepay/Exception/ArchiveNotAccessibleException.php b/Applepay/Exception/ArchiveNotAccessibleException.php deleted file mode 100644 index eeb2f613..00000000 --- a/Applepay/Exception/ArchiveNotAccessibleException.php +++ /dev/null @@ -1,13 +0,0 @@ -getCode(), - $exception - ); - } - - public static function fromResponse(Response $response): self - { - return new self( - 'Could not download Adyen ApplePay merchant id domain association file. The returned download response ' . - 'is with status ' . $response->getStatusCode(), - $response->getStatusCode() - ); - } -} diff --git a/Applepay/Exception/FileNotWrittenException.php b/Applepay/Exception/FileNotWrittenException.php deleted file mode 100644 index 31d36cf8..00000000 --- a/Applepay/Exception/FileNotWrittenException.php +++ /dev/null @@ -1,13 +0,0 @@ -|InstallResult[] - */ - public function __invoke(): \Generator; -} diff --git a/Applepay/MerchantAssociation/InstallHandler/ArchiveInstaller.php b/Applepay/MerchantAssociation/InstallHandler/ArchiveInstaller.php deleted file mode 100755 index 103085ec..00000000 --- a/Applepay/MerchantAssociation/InstallHandler/ArchiveInstaller.php +++ /dev/null @@ -1,53 +0,0 @@ -archivePath = $archivePath; - $this->storageFilesystem = $storageFilesystem; - } - - public function isFallback(): bool - { - return true; - } - - public function install(): void - { - $archive = new \ZipArchive(); - $result = $archive->open($this->archivePath); - if (true !== $result) { - throw ArchiveNotAccessibleException::fromErrorCode((int) $result, $this->archivePath); - } - - $this->storageFilesystem->resetStorage(); - $extracted = $archive->extractTo( - dirname($this->storageFilesystem->storagePath()), - [self::ARCHIVED_FILE_NAME] - ); - if (!$extracted) { - $archive->close(); - - throw ArchiveNotExtractedException::fromPaths($this->archivePath, $this->storageFilesystem->storagePath()); - } - $this->storageFilesystem->updateFilePermissions(); - $archive->close(); - } -} diff --git a/Applepay/MerchantAssociation/InstallHandler/DownloadInstaller.php b/Applepay/MerchantAssociation/InstallHandler/DownloadInstaller.php deleted file mode 100755 index 8930ee64..00000000 --- a/Applepay/MerchantAssociation/InstallHandler/DownloadInstaller.php +++ /dev/null @@ -1,71 +0,0 @@ -client = $client; - $this->storageFilesystem = $storageFilesystem; - $this->logger = $logger; - $this->baseUri = $baseUri; - } - - public function isFallback(): bool - { - return false; - } - - public function install(): void - { - try { - $this->storageFilesystem->resetStorage(); - - $url = $this->baseUri . '/.well-known/apple-developer-merchantid-domain-association'; - - $this->logger->info("Sending request:\n GET $url"); - $response = $this->client->get($url); - - $this->logger->info( - "Received response:\n".$response->getStatusCode(), - ['response' => $response] - ); - - if ((int)$response->getStatusCode() > 400) { - $this->logger->error('Error completing request', ['response' => $response]); - throw FileNotDownloadedException::fromResponse($response); - } - - $this->storageFilesystem->createFile($this->storageFilesystem->storagePath(), $response->getBody()); - $this->storageFilesystem->updateFilePermissions(); - } catch (RequestException $exception) { - throw FileNotDownloadedException::fromException($exception); - } - } -} diff --git a/Applepay/MerchantAssociation/InstallHandler/Installer.php b/Applepay/MerchantAssociation/InstallHandler/Installer.php deleted file mode 100644 index 3f0a96fb..00000000 --- a/Applepay/MerchantAssociation/InstallHandler/Installer.php +++ /dev/null @@ -1,12 +0,0 @@ -installers = $installers; - } - - /** - * {@inheritdoc} - */ - public function __invoke(): \Generator - { - foreach ($this->installers as $installer) { - try { - $installer->install(); - - yield InstallResult::fromSuccess() - ->withFallback($installer->isFallback()); - - return; - } catch (\Exception $exception) { - yield InstallResult::fromException($exception) - ->withFallback($installer->isFallback()); - } - } - } -} diff --git a/Applepay/MerchantAssociation/Model/InstallResult.php b/Applepay/MerchantAssociation/Model/InstallResult.php deleted file mode 100755 index caef285b..00000000 --- a/Applepay/MerchantAssociation/Model/InstallResult.php +++ /dev/null @@ -1,60 +0,0 @@ -success = $success; - $this->fallback = false; - $this->exception = null; - } - - public static function fromSuccess(): self - { - return new self(true); - } - - public static function fromException(\Exception $exception): self - { - $new = new self(false); - $new->exception = $exception; - - return $new; - } - - public function withFallback(bool $fallback): self - { - $new = clone $this; - $new->fallback = $fallback; - - return $new; - } - - public function fallback(): bool - { - return $this->fallback; - } - - public function success(): bool - { - return $this->success; - } - - public function exception(): ?\Exception - { - return $this->exception; - } -} diff --git a/Applepay/MerchantAssociation/RewriteUrl/UrlWriter.php b/Applepay/MerchantAssociation/RewriteUrl/UrlWriter.php deleted file mode 100644 index a7e07461..00000000 --- a/Applepay/MerchantAssociation/RewriteUrl/UrlWriter.php +++ /dev/null @@ -1,10 +0,0 @@ -filesystem = $filesystem; - $this->storagePath = $storagePath; - } - - public function storageFileExists(): bool - { - return $this->filesystem->exists($this->storagePath); - } - - public function storagePath(): string - { - return $this->storagePath; - } - - public function resetStorage(): void - { - $this->createDirectory(); - $this->removeFile(); - } - - public function updateFilePermissions(): void - { - if (!$this->filesystem->exists($this->storagePath)) { - return; - } - - $this->filesystem->chmod($this->storagePath, 0664); - } - - public function createDirectory(): void - { - $directory = dirname($this->storagePath); - if ($this->filesystem->exists($directory)) { - return; - } - - $this->filesystem->mkdir($directory); - $this->filesystem->chmod($directory, 0764); - } - - public function createFile($fileName, $content) - { - $this->filesystem->dumpFile($fileName, $content); - } - - private function removeFile(): void - { - if (!$this->filesystem->exists($this->storagePath)) { - return; - } - - $this->filesystem->remove($this->storagePath); - } -} diff --git a/Applepay/MerchantAssociation/TraceableFileInstaller.php b/Applepay/MerchantAssociation/TraceableFileInstaller.php deleted file mode 100644 index a2a9a69c..00000000 --- a/Applepay/MerchantAssociation/TraceableFileInstaller.php +++ /dev/null @@ -1,43 +0,0 @@ -installer = $installer; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function __invoke(): \Generator - { - $installers = ($this->installer)(); - foreach ($installers as $installResult) { - if ($installResult->success()) { - yield $installResult; - - continue; - } - - if ($installResult->exception() instanceof \Exception) { - $this->logger->error($installResult->exception()->getMessage()); - } - - yield $installResult; - } - } -} diff --git a/Basket/Restore/DetailAttributesRestorer.php b/Basket/Restore/DetailAttributesRestorer.php deleted file mode 100755 index b1a9abe6..00000000 --- a/Basket/Restore/DetailAttributesRestorer.php +++ /dev/null @@ -1,83 +0,0 @@ -modelManager = $modelManager; - $this->basketDetailAttributes = $basketDetailAttributes; - $this->orderDetailAttributes = $orderDetailAttributes; - } - - /** - * Copies attributes from the supplied order detail article ID to a basket detail ID. - * - * @throws Zend_Db_Adapter_Exception - */ - public function restore(int $orderDetailId, int $basketDetailId): void - { - $orderDetailAttributes = $this->orderDetailAttributes->fetchByOrderDetailId((string) $orderDetailId); - if (!count($orderDetailAttributes)) { - return; - } - - $attributes = $this->provideFillableAttributeColumns(); - - if (!count($attributes)) { - return; - } - - // Updating the basket attributes with the order attribute values - $attributeValues = []; - foreach ($attributes as $attribute) { - if (!empty($orderDetailAttributes[$attribute])) { - $attributeValues[$attribute] = $orderDetailAttributes[$attribute]; - } - } - - if (!count($attributeValues)) { - return; - } - - $this->basketDetailAttributes->hasBasketDetails($basketDetailId) - ? $this->basketDetailAttributes->update($basketDetailId, $attributeValues) - : $this->basketDetailAttributes->insert($basketDetailId, $attributeValues); - } - - private function provideFillableAttributeColumns(): array - { - // Getting order attributes columns to possibly fill - $basketAttributesColumns = $this->modelManager - ->getClassMetadata('Shopware\Models\Attribute\OrderDetail') - ->getColumnNames(); - - // These columns shouldn't be translated from the order detail to the basket detail - $columnsToSkip = [ - 'id', - 'detailID', - ]; - - return array_diff($basketAttributesColumns, $columnsToSkip); - } -} diff --git a/Bootstrap/Bootstrap.php b/Bootstrap/Bootstrap.php new file mode 100644 index 00000000..1df08d32 --- /dev/null +++ b/Bootstrap/Bootstrap.php @@ -0,0 +1,352 @@ +Container()->get(LoggerService::class); + } + ); + + ServiceRegister::registerService( + Configuration::CLASS_NAME, + static function () { + return ConfigurationService::getInstance(); + } + ); + + ServiceRegister::registerService( + HttpClient::CLASS_NAME, + static function () { + return new CurlHttpClient(); + } + ); + + ServiceRegister::registerService( + FileService::class, + static function () { + return new FileService(Shopware()->Container()->get('shopware_media.media_service')); + } + ); + + ServiceRegister::registerService( + OrderServiceInterface::class, + static function () { + return Shopware()->Container()->get(OrderService::class); + } + ); + + ServiceRegister::registerService( + ShopPaymentService::class, + static function () { + return new PaymentMethodService( + ServiceRegister::getService(StoreContext::class), + Shopware()->Models(), + Shopware()->Container()->get(StoreRepository::class), + Shopware()->Container()->get('shopware.plugin_payment_installer'), + ServiceRegister::getService(FileService::class), + ServiceRegister::getService(PaymentMethodConfigRepository::class), + ServiceRegister::getService(StoreServiceInterface::class) + ); + } + ); + + ServiceRegister::registerService( + StoreServiceInterface::class, + static function () { + return new StoreService( + Shopware()->Container()->get(StoreRepository::class), + Shopware()->Container()->get(OrderRepository::class), + RepositoryRegistry::getRepository(ConnectionSettings::getClassName()) + ); + } + ); + + ServiceRegister::registerService( + WebhookUrlService::class, + static function () { + return new \AdyenPayment\Components\Integration\WebhookUrlService( + ServiceRegister::getService(StoreContext::class) + ); + } + ); + + // Override WebhookHandler to swap old plugin merchant reference to a new one (old order number -> new order temporary id) + ServiceRegister::registerService( + WebhookHandler::class, + new SingleInstance(static function () { + return new LegacyMerchantReferenceNormalizationWebhookHandler( + Shopware()->Container()->get(OrderRepository::class), + ServiceRegister::getService(WebhookSynchronizationServiceInterface::class), + ServiceRegister::getService(QueueService::class) + ); + }) + ); + + ServiceRegister::registerService( + BaseTransactionDetailsServiceAlias::class, + static function () { + return new TransactionDetailsService( + ServiceRegister::getService(ConnectionService::class), + ServiceRegister::getService(TransactionHistoryService::class) + ); + } + ); + + ServiceRegister::registerService( + LastOpenTimeService::class, + static function () { + return new LastOpenTimeService( + RepositoryRegistry::getRepository(LastOpenTime::getClassName()) + ); + } + ); + + ServiceRegister::registerService( + SystemInfoServiceInterface::class, + static function () { + return new SystemInfoService( + ServiceRegister::getService(Configuration::CLASS_NAME), + Shopware()->Container()->get(StoreRepository::class)); + } + ); + + ServiceRegister::registerService( + UninstallService::class, + static function () { + return new UninstallService( + ServiceRegister::getService(StoreServiceInterface::class) + ); + } + ); + } + + public static function initPaymentRequestProcessors(): void + { + parent::initPaymentRequestProcessors(); + + ServiceRegister::registerService( + L2L3DataProcessor::class, + static function () { + /** @noinspection NullPointerExceptionInspection */ + return new IntegrationL2L3DataProcessor( + ServiceRegister::getService(PaymentService::class), + Shopware()->Container()->get('models')->getRepository(Country::class) + ); + } + ); + + ServiceRegister::registerService( + BasketItemsProcessor::class, + static function () { + /** @noinspection NullPointerExceptionInspection */ + return new \AdyenPayment\Components\Integration\PaymentProcessors\BasketItemsProcessor( + ServiceRegister::getService(GeneralSettingsService::class), + Shopware()->Container()->get('models')->getRepository(Article::class) + ); + } + ); + + ServiceRegister::registerService( + AddressProcessor::class, + static function () { + /** @noinspection NullPointerExceptionInspection */ + return new IntegrationAddressProcessor( + Shopware()->Container()->get('models')->getRepository(Country::class) + ); + } + ); + + ServiceRegister::registerService( + BirthdayProcessor::class, + static function () { + return new IntegrationBirthdayProcessor(); + } + ); + + ServiceRegister::registerService( + LineItemsProcessor::class, + static function () { + /** @noinspection NullPointerExceptionInspection */ + return new IntegrationLineItemsProcessor( + Shopware()->Container()->get('models')->getRepository(Article::class) + ); + } + ); + + ServiceRegister::registerService( + ShopperEmailProcessor::class, + static function () { + return new IntegrationShopperEmailProcessor(); + } + ); + + ServiceRegister::registerService( + ShopperNameProcessor::class, + static function () { + return new IntegrationShopperNameProcessor(); + } + ); + + ServiceRegister::registerService( + ShopperReferenceProcessor::class, + static function () { + return new IntegrationShopperReferenceProcessor(); + } + ); + + ServiceRegister::registerService( + \Adyen\Core\BusinessLogic\Domain\Integration\Processors\ShopperLocaleProcessor::class, + static function () { + return new ShopperLocaleProcessor(); + } + ); + + ServiceRegister::registerService( + VersionService::class, + static function () { + return new \AdyenPayment\Components\Integration\VersionService(); + } + ); + } + + /** + * @inheritDoc + * + * @throws RepositoryClassException + */ + protected static function initRepositories(): void + { + parent::initRepositories(); + + RepositoryRegistry::registerRepository(Process::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(ConfigEntity::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(QueueItem::getClassName(), QueueItemRepository::getClassName()); + RepositoryRegistry::registerRepository(LogData::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository( + ConnectionSettings::getClassName(), + BaseRepositoryWithConditionalDeletes::getClassName() + ); + RepositoryRegistry::registerRepository(AdyenGivingSettings::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(GeneralSettings::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(WebhookConfig::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(PaymentMethod::getClassName(), PaymentMethodRepository::getClassName()); + RepositoryRegistry::registerRepository(OrderStatusMapping::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository( + TransactionHistory::getClassName(), + TransactionLogRepository::getClassName() + ); + RepositoryRegistry::registerRepository( + TransactionLog::getClassName(), + TransactionLogRepository::getClassName() + ); + RepositoryRegistry::registerRepository(DonationsData::getClassName(), AdyenGivingRepository::getClassName()); + RepositoryRegistry::registerRepository(Notification::getClassName(), NotificationsRepository::getClassName()); + RepositoryRegistry::registerRepository(LastOpenTime::getClassName(), BaseRepository::getClassName()); + RepositoryRegistry::registerRepository(DisconnectTime::getClassName(), BaseRepository::getClassName()); + } +} diff --git a/Collection/Payment/PaymentMeanCollection.php b/Collection/Payment/PaymentMeanCollection.php deleted file mode 100644 index fc78a25d..00000000 --- a/Collection/Payment/PaymentMeanCollection.php +++ /dev/null @@ -1,134 +0,0 @@ -paymentMeans = $paymentMeans; - } - - public static function createFromShopwareArray(array $paymentMeans): self - { - return new self(...array_map( - static function(array $paymentMean): PaymentMean { - return PaymentMean::createFromShopwareArray($paymentMean); - }, - $paymentMeans - )); - } - - /** - * @return \Generator - */ - public function getIterator(): \Traversable - { - yield from $this->paymentMeans; - } - - public function count(): int - { - return \count($this->paymentMeans); - } - - public function map(callable $callable): array - { - return array_filter(array_map($callable, $this->paymentMeans)); - } - - public function filter(callable $filter): self - { - return new self(...array_filter($this->paymentMeans, $filter)); - } - - public function filterBySource(SourceType $source): self - { - return $this->filter( - static function(PaymentMean $paymentMean) use ($source): bool { - return $source->equals($paymentMean->getSource()); - } - ); - } - - public function filterExcludeAdyen(): self - { - return $this->filter( - static function(PaymentMean $paymentMean): bool { - return !$paymentMean->getSource()->equals(SourceType::adyen()); - } - ); - } - - public function filterExcludeHidden(): self - { - return new self(...array_filter( - $this->paymentMeans, - static function(PaymentMean $paymentMean): bool { - return !$paymentMean->isHidden(); - } - )); - } - - public function fetchStoredMethodUmbrellaPaymentMean(): ?PaymentMean - { - foreach ($this->paymentMeans as $paymentMean) { - if (AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE === $paymentMean->getValue('name')) { - return $paymentMean; - } - } - - return null; - } - - public function fetchById(int $paymentId): ?PaymentMean - { - foreach ($this->paymentMeans as $paymentMean) { - if ($paymentMean->getId() === $paymentId) { - return $paymentMean; - } - } - - return null; - } - - public function fetchByStoredMethodId(string $storedMethodId): ?PaymentMean - { - foreach ($this->paymentMeans as $paymentMean) { - if ($paymentMean->getValue('stored_method_id') === $storedMethodId) { - return $paymentMean; - } - } - - return null; - } - - public function fetchByUmbrellaStoredMethodId(string $storedMethodId): ?PaymentMean - { - foreach ($this->paymentMeans as $paymentMean) { - if ($paymentMean->getValue('stored_method_umbrella_id') === $storedMethodId) { - return $paymentMean; - } - } - - return null; - } - - public function toShopwareArray(): array - { - return array_reduce($this->paymentMeans, static function(array $payload, PaymentMean $paymentMean): array { - $payload[$paymentMean->getId()] = $paymentMean->getRaw(); - - return $payload; - }, []); - } -} diff --git a/Collection/Payment/PaymentMethodCollection.php b/Collection/Payment/PaymentMethodCollection.php deleted file mode 100755 index de9b63f7..00000000 --- a/Collection/Payment/PaymentMethodCollection.php +++ /dev/null @@ -1,132 +0,0 @@ -paymentMethods = $paymentMethods; - } - - /** - * @return \Generator - */ - public function getIterator(): \Traversable - { - yield from $this->paymentMethods; - } - - public function count(): int - { - return count($this->paymentMethods); - } - - public static function fromAdyenMethods(array $adyenMethods): self - { - return new self( - ...array_map( - static function(array $paymentMethod) { - return PaymentMethod::fromRaw($paymentMethod); - }, - $adyenMethods['paymentMethods'] ?? [] - ), - ...array_map( - static function(array $paymentMethod) { - return PaymentMethod::fromRaw($paymentMethod); - }, - $adyenMethods['storedPaymentMethods'] ?? [] - ) - ); - } - - public function withImportLocale(PaymentMethodCollection $importLocalePaymentMethods): self - { - $importPaymentMethods = iterator_to_array($importLocalePaymentMethods); - - return new self(...array_map( - static function(int $index, PaymentMethod $paymentMethod) use ($importPaymentMethods): PaymentMethod { - /** @var PaymentMethod $importMethod */ - $importLocaleMethod = $importPaymentMethods[$index] ?? null; - if (!$importLocaleMethod) { - return $paymentMethod; - } - - return $paymentMethod->withCode($importLocaleMethod->name()); - }, - array_keys($this->paymentMethods), - array_values($this->paymentMethods) - )); - } - - public function map(callable $callback): array - { - return array_map($callback, $this->paymentMethods); - } - - public function mapToRaw(): array - { - return array_map( - static function(PaymentMethod $paymentMethod) { - return $paymentMethod->rawData(); - }, - $this->paymentMethods - ); - } - - /** - * $identifierOrStoredId is the Adyen "unique identifier" or Adyen "stored payment id" - * NOT the Shopware id. - */ - private function fetchByIdentifierOrStoredId(string $identifierOrStoredId): ?PaymentMethod - { - foreach ($this->paymentMethods as $paymentMethod) { - if ($paymentMethod->getStoredPaymentMethodId() === $identifierOrStoredId) { - return $paymentMethod; - } - - if ($paymentMethod->code() === $identifierOrStoredId) { - return $paymentMethod; - } - } - - return null; - } - - public function fetchByPaymentMean(PaymentMean $paymentMean): ?PaymentMethod - { - if ('' === $paymentMean->getAdyenStoredMethodId() && '' === $paymentMean->getAdyenCode()) { - return null; - } - - if ($paymentMean->getAdyenStoredMethodId()) { - return $this->fetchByIdentifierOrStoredId($paymentMean->getAdyenStoredMethodId()); - } - - return $this->fetchByIdentifierOrStoredId($paymentMean->getAdyenCode()); - } - - public function filter(callable $filter): self - { - return new self(...array_filter($this->paymentMethods, $filter)); - } - - public function filterByPaymentType(PaymentGroup $group): self - { - return new self(...array_filter( - $this->paymentMethods, - static function(PaymentMethod $paymentMethod) use ($group) { - return $paymentMethod->group()->equals($group); - } - )); - } -} diff --git a/Commands/ImportPaymentMethodsCommand.php b/Commands/ImportPaymentMethodsCommand.php deleted file mode 100755 index c1e9ad0b..00000000 --- a/Commands/ImportPaymentMethodsCommand.php +++ /dev/null @@ -1,58 +0,0 @@ -importer = $importer; - parent::__construct(); - } - - protected function configure(): void - { - $this->setDescription('Import Adyen payment methods for all stores'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $total = $success = 0; - $io = new SymfonyStyle($input, $output); - - foreach ($this->importer->importAll() as $result) { - ++$total; - - if (!$result->isSuccess()) { - $io->warning(sprintf('Could not import payment method %s for store %s, message: %s.', - $result->getPaymentMethod() ? $result->getPaymentMethod()->adyenType()->type() : 'n/a', - $result->getShop()->getName(), - $result->getException() ? $result->getException()->getMessage() : 'n/a' - )); - - continue; - } - - ++$success; - $io->text(sprintf('Imported payment method %s for store %s', - $result->getPaymentMethod() ? $result->getPaymentMethod()->adyenType()->type() : 'n/a', - $result->getShop()->getName() - )); - } - - $io->success(sprintf('Successfully imported %s of %s Payment Method(s)', $success, $total)); - - return 0; - } -} diff --git a/Commands/ProcessNotifications.php b/Commands/ProcessNotifications.php deleted file mode 100755 index f7ddc7c6..00000000 --- a/Commands/ProcessNotifications.php +++ /dev/null @@ -1,86 +0,0 @@ -loader = $fifoNotificationLoader; - $this->notificationProcessor = $notificationProcessor; - - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure(): void - { - $this - ->setDescription('Process notifications in queue') - ->addOption( - 'number', - 'no', - InputOption::VALUE_OPTIONAL, - 'Number of notifications to process. Defaults to '. - ProcessNotificationsCronjob::NUMBER_OF_NOTIFICATIONS_TO_HANDLE.'.', - ProcessNotificationsCronjob::NUMBER_OF_NOTIFICATIONS_TO_HANDLE - ); - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Enlight_Event_Exception - */ - protected function execute(InputInterface $input, OutputInterface $output): void - { - $number = $input->getOption('number'); - - $feedback = $this->notificationProcessor->processMany( - $this->loader->load((int) $number) - ); - - $totalCount = 0; - $successCount = 0; - - /** @var NotificationProcessorFeedback $item */ - foreach ($feedback as $item) { - ++$totalCount; - $successCount += (int) $item->isSuccess(); - $output->writeln($item->getNotification()->getId().': '.$item->getMessage()); - } - - $output->writeln(sprintf( - 'Imported %d items. %s OK, %s FAILED', - $totalCount, - $successCount, - $totalCount - $successCount - )); - } -} diff --git a/Components/Adyen/Builder/PaymentMethodOptionsBuilder.php b/Components/Adyen/Builder/PaymentMethodOptionsBuilder.php deleted file mode 100644 index df5e1225..00000000 --- a/Components/Adyen/Builder/PaymentMethodOptionsBuilder.php +++ /dev/null @@ -1,37 +0,0 @@ -Session() - ->sOrderVariables['sUserData']['additional']['country']['countryiso'] ?? '' - ); - if (!$countryCode) { - $countryCode = (string) ( - Shopware()->Modules()->Admin() - ->sGetUserData()['additional']['country']['countryiso'] - ); - } - - $currencyName = Shopware()->Session()->sOrderVariables['sBasket']['sCurrencyName'] ?? ''; - $currency = $currencyName ?: Shopware()->Shop()->getCurrency()->getCurrency(); - - $value = (float) ( - Shopware()->Session()->sOrderVariables['sBasket']['AmountNumeric'] - ?? Shopware()->Modules()->Basket()->sGetAmount()['totalAmount'] - ?? 1.0 - ); - - return [ - 'countryCode' => $countryCode, - 'currency' => $currency, - 'value' => $value, - ]; - } -} diff --git a/Components/Adyen/Builder/PaymentMethodOptionsBuilderInterface.php b/Components/Adyen/Builder/PaymentMethodOptionsBuilderInterface.php deleted file mode 100644 index 47452008..00000000 --- a/Components/Adyen/Builder/PaymentMethodOptionsBuilderInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -paymentMethodService = $paymentMethodService; - $this->paymentMethodOptionsBuilder = $paymentMethodOptionsBuilder; - $this->paymentMethodEnricher = $paymentMethodEnricher; - } - - public function __invoke(PaymentMeanCollection $paymentMeans): PaymentMeanCollection - { - $paymentMethodOptions = ($this->paymentMethodOptionsBuilder)(); - if (0.0 === $paymentMethodOptions['value']) { - return $paymentMeans->filterExcludeAdyen(); - } - - $adyenPaymentMethods = $this->paymentMethodService->getPaymentMethods( - $paymentMethodOptions['countryCode'], - $paymentMethodOptions['currency'], - $paymentMethodOptions['value'] - ); - - $umbrellaPaymentMean = $paymentMeans->fetchStoredMethodUmbrellaPaymentMean(); - if (null === $umbrellaPaymentMean) { - throw UmbrellaPaymentMeanNotFoundException::missingUmbrellaPaymentMean(); - } - - return new PaymentMeanCollection( - ...$this->provideEnrichedPaymentMeans($paymentMeans, $adyenPaymentMethods), - ...$this->provideEnrichedStoredPaymentMeans($adyenPaymentMethods, $umbrellaPaymentMean) - ); - } - - private function provideEnrichedPaymentMeans( - PaymentMeanCollection $paymentMeans, - PaymentMethodCollection $adyenPaymentMethods - ): array { - $enricher = $this->paymentMethodEnricher; - - return $paymentMeans - ->filterExcludeHidden() - ->map(static function(PaymentMean $paymentMean) use ($adyenPaymentMethods, $enricher): ?PaymentMean { - if (!$paymentMean->getSource()->equals(SourceType::adyen())) { - return $paymentMean; - } - - $paymentMethod = $adyenPaymentMethods->fetchByPaymentMean($paymentMean); - if (null === $paymentMethod) { - return null; - } - - return PaymentMean::createFromShopwareArray(($enricher)($paymentMean->getRaw(), $paymentMethod)); - }); - } - - private function provideEnrichedStoredPaymentMeans( - PaymentMethodCollection $adyenPaymentMethods, - PaymentMean $umbrellaPaymentMean - ): array { - $enricher = $this->paymentMethodEnricher; - $storedAdyenMethods = $adyenPaymentMethods->filterByPaymentType(PaymentGroup::stored()); - - return $storedAdyenMethods->map( - static function(PaymentMethod $paymentMethod) use ($umbrellaPaymentMean, $enricher): PaymentMean { - return PaymentMean::createFromShopwareArray( - ($enricher)($umbrellaPaymentMean->getRaw(), $paymentMethod) - ); - } - ); - } -} diff --git a/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderInterface.php b/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderInterface.php deleted file mode 100644 index 2ad862e8..00000000 --- a/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderInterface.php +++ /dev/null @@ -1,12 +0,0 @@ - 'card', - 'yandex_money' => 'yandex', - ]; - - public function provideByType(string $type): string - { - //Some payment method codes don't match the logo filename - $logoType = self::PM_LOGO_FILENAME[$type] ?? $type; - - return sprintf('https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/%s.svg', $logoType); - } -} diff --git a/Components/Adyen/PaymentMethod/ImageLogoProviderInterface.php b/Components/Adyen/PaymentMethod/ImageLogoProviderInterface.php deleted file mode 100644 index 57dd24f8..00000000 --- a/Components/Adyen/PaymentMethod/ImageLogoProviderInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -configuration = $configuration; - $this->adyenApiFactory = $adyenApiFactory; - $this->logger = $logger; - } - - public function __invoke(Shop $shop): PaymentMethodCollection - { - try { - $merchantAccount = $this->configuration->getMerchantAccount($shop); - $adyenClient = $this->adyenApiFactory->provide($shop); - $checkout = new Checkout($adyenClient); - - $paymentMethods = PaymentMethodCollection::fromAdyenMethods($checkout->paymentMethods([ - 'merchantAccount' => $merchantAccount, - 'shopperLocale' => PaymentMethodService::IMPORT_LOCALE, - ])); - - return $paymentMethods->withImportLocale($paymentMethods); - } catch (AdyenException $e) { - $this->logger->error($e->getMessage(), [ - 'merchantAccount' => $merchantAccount ?? 'n/a', - 'Shop' => $shop->getName(), - 'trace' => $e->getTraceAsString(), - ]); - } - - return new PaymentMethodCollection(); - } -} diff --git a/Components/Adyen/PaymentMethod/PaymentMethodsProviderInterface.php b/Components/Adyen/PaymentMethod/PaymentMethodsProviderInterface.php deleted file mode 100644 index df4acfe1..00000000 --- a/Components/Adyen/PaymentMethod/PaymentMethodsProviderInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; - $this->connection = $connection; - } - - public function fromRequest(Enlight_Controller_Request_Request $request): ?PaymentMean - { - $registerPayment = $request->getParam('register', [])['payment'] ?? null; - if (null === $registerPayment) { - return null; - } - - $enrichedPaymentMeans = ($this->enrichedPaymentMeanProvider)( - PaymentMeanCollection::createFromShopwareArray($this->fetchUmbrellaMethod()) - ); - - return $enrichedPaymentMeans->fetchByUmbrellaStoredMethodId($registerPayment); - } - - private function fetchUmbrellaMethod(): array - { - $queryBuilder = $this->connection->createQueryBuilder(); - - return $queryBuilder->select('*') - ->from('s_core_paymentmeans') - ->where('name = :umbrellaMethodName') - ->setParameter(':umbrellaMethodName', AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE) - ->execute() - ->fetchAll(); - } -} diff --git a/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderInterface.php b/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderInterface.php deleted file mode 100644 index e7ade33a..00000000 --- a/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; - $this->logger = $logger; - } - - /** - * @throws \Adyen\AdyenException - */ - public function __invoke(PaymentMeanCollection $paymentMeans): PaymentMeanCollection - { - try { - return ($this->enrichedPaymentMeanProvider)($paymentMeans); - } catch (\Exception $exception) { - $this->logger->critical($exception->getMessage(), ['exception' => $exception]); - } - - return new PaymentMeanCollection(); - } -} diff --git a/Components/Adyen/PaymentMethodService.php b/Components/Adyen/PaymentMethodService.php deleted file mode 100755 index b9d928ba..00000000 --- a/Components/Adyen/PaymentMethodService.php +++ /dev/null @@ -1,124 +0,0 @@ -apiClientMap = $apiClientMap; - $this->configuration = $configuration; - $this->logger = $logger; - $this->customerNumberProvider = $customerNumberProvider; - } - - public function getPaymentMethods( - ?string $countryCode = null, - ?string $currency = null, - ?float $value = null, - ?string $locale = null, - bool $cache = true - ): PaymentMethodCollection { - $cache = $cache && $this->configuration->isPaymentmethodsCacheEnabled(); - $cacheKey = $this->getCacheKey($countryCode ?? '', $currency ?? '', (string) ($value ?? '')); - if ($cache && isset($this->cache[$cacheKey])) { - return $this->cache[$cacheKey]; - } - - $locale = $locale ?: Shopware()->Shop()->getLocale()->getLocale(); - - $checkout = $this->getCheckout(); - $adyenCurrency = new Currency(); - - $requestParams = [ - 'merchantAccount' => $this->configuration->getMerchantAccount(), - 'countryCode' => $countryCode, - 'amount' => [ - 'currency' => $currency, - 'value' => $adyenCurrency->sanitize($value, $currency), - ], - 'channel' => Channel::WEB, - 'shopperLocale' => $locale, - 'shopperReference' => ($this->customerNumberProvider)(), - ]; - - try { - $paymentMethods = PaymentMethodCollection::fromAdyenMethods( - $checkout->paymentMethods($requestParams) - ); - - // get payment methods import locale (important for code) - $paymentMethods = self::IMPORT_LOCALE === $locale - ? $paymentMethods->withImportLocale($paymentMethods) - : $paymentMethods->withImportLocale( - PaymentMethodCollection::fromAdyenMethods($checkout->paymentMethods( - array_replace($requestParams, ['shopperLocale' => self::IMPORT_LOCALE]) - )) - ); - } catch (AdyenException $e) { - $this->logger->critical('Adyen Exception', [ - 'message' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'errorType' => $e->getErrorType(), - 'status' => $e->getStatus(), - ]); - - return new PaymentMethodCollection(); - } - - if ($cache) { - $this->cache[$cacheKey] = $paymentMethods; - } - - return $paymentMethods; - } - - private function getCacheKey(string ...$keys): string - { - return md5(implode(',', $keys)); - } - - public function getCheckout(): Checkout - { - return new Checkout( - $this->apiClientMap->lookup( - Shopware()->Shop() - ) - ); - } -} diff --git a/Components/Adyen/PaymentMethodServiceInterface.php b/Components/Adyen/PaymentMethodServiceInterface.php deleted file mode 100644 index d7344319..00000000 --- a/Components/Adyen/PaymentMethodServiceInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -apiClientMap = $apiClientMap; - $this->modelManager = $modelManager; - $this->notificationManager = $notificationManager; - } - - /** - * @throws AdyenException - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function doRefund(int $orderId): Refund - { - /** @var Order $order */ - $order = $this->modelManager->find(Order::class, $orderId); - $modification = new Modification( - $this->apiClientMap->lookup($order->getShop()) - ); - - $notification = $this->provideNotification($orderId); - $request = [ - 'originalReference' => $notification->getPspReference(), - 'modificationAmount' => [ - 'value' => $notification->getAmountValue(), - 'currency' => $notification->getAmountCurrency(), - ], - 'merchantAccount' => $notification->getMerchantAccountCode(), - ]; - $refund = $modification->refund($request); - - $orderRefund = new Refund(); - $orderRefund->setOrderId($orderId); - $orderRefund->setCreatedAt(new \DateTime()); - $orderRefund->setUpdatedAt(new \DateTime()); - $orderRefund->setPspReference($refund['pspReference']); - $this->modelManager->persist($orderRefund); - $this->modelManager->flush(); - - return $orderRefund; - } - - /** - * @throws NonUniqueResultException - */ - private function provideNotification(int $orderId): Notification - { - return $this->notificationManager->getAuthorisationNotificationForOrderId($orderId); - } -} diff --git a/Applepay/MerchantAssociation/RewriteUrl/SeoUrlWriter.php b/Components/ApplePay/SeoUrlWriter.php similarity index 81% rename from Applepay/MerchantAssociation/RewriteUrl/SeoUrlWriter.php rename to Components/ApplePay/SeoUrlWriter.php index 8358a394..c0257d00 100755 --- a/Applepay/MerchantAssociation/RewriteUrl/SeoUrlWriter.php +++ b/Components/ApplePay/SeoUrlWriter.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace AdyenPayment\Applepay\MerchantAssociation\RewriteUrl; +namespace AdyenPayment\Components\ApplePay; use Shopware_Components_Modules; -final class SeoUrlWriter implements UrlWriter +final class SeoUrlWriter { /** @var Shopware_Components_Modules */ private $modules; diff --git a/Components/BasketHelper.php b/Components/BasketHelper.php new file mode 100644 index 00000000..56a4acd9 --- /dev/null +++ b/Components/BasketHelper.php @@ -0,0 +1,96 @@ +basket = $basket; + $this->connection = $connection; + $this->session = $session; + } + + public function forceBasketContentFor(string $articleOrderNumber): void + { + $this->basket->sDeleteBasket(); + $this->basket->sAddArticle($articleOrderNumber); + $this->basket->sRefreshBasket(); + } + + public function getTotalAmountFor( + Shopware_Controllers_Frontend_Checkout $coController, + ?string $articleOrderNumber = null + ): Amount { + if (!$articleOrderNumber) { + return $this->getCurrentCartAmount($coController); + } + + $this->backupCurrentBasket(); + $this->forceBasketContentFor($articleOrderNumber); + $totalAmount = $this->getCurrentCartAmount($coController); + $this->restoreBasketFromBackup(); + + + return $totalAmount; + } + + private function getCurrentCartAmount(Shopware_Controllers_Frontend_Checkout $coController): Amount + { + $basket = $coController->getBasket(); + $totalAmount = array_key_exists('sAmountWithTax', $basket) ? $basket['sAmountWithTax'] : $basket['sAmount']; + $currencyName = Shopware()->Shop() ? Shopware()->Shop()->getCurrency()->getCurrency() : null; + + return Amount::fromFloat( + $totalAmount, + $currencyName ? Currency::fromIsoCode($currencyName) : Currency::getDefault() + ); + } + + private function backupCurrentBasket(): void + { + $this->connection->update( + 's_order_basket', + ['sessionID' => $this->session->get('sessionId') . '_adyen_backup'], + ['sessionID' => $this->session->get('sessionId')] + ); + } + + private function restoreBasketFromBackup(): void + { + $this->basket->sDeleteBasket(); + + $this->connection->update( + 's_order_basket', + ['sessionID' => $this->session->get('sessionId')], + ['sessionID' => $this->session->get('sessionId') . '_adyen_backup'] + ); + + $this->basket->sRefreshBasket(); + } +} diff --git a/Components/BasketService.php b/Components/BasketService.php deleted file mode 100644 index 12feeaae..00000000 --- a/Components/BasketService.php +++ /dev/null @@ -1,311 +0,0 @@ -sBasket = Shopware()->Modules()->Basket(); - $this->events = $events; - $this->modelManager = $modelManager; - $this->db = $db; - $this->session = $session; - - $this->statusRepository = $modelManager->getRepository(Status::class); - $this->orderRepository = $modelManager->getRepository(Order::class); - $this->voucherRepository = $modelManager->getRepository(Voucher::class); - $this->voucherCodeRepository = $modelManager->getRepository(Code::class); - $this->detailAttributesRestorer = $detailAttributesRestorer; - } - - /** - * @throws ORMException - * @throws OptimisticLockException - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - */ - public function cancelAndRestoreByOrderNumber(string $orderNumber): void - { - $order = $this->getOrderByOrderNumber($orderNumber); - if (!$order) { - return; - } - - $this->restoreFromOrder($order); - $this->cancelOrder($order); - } - - public function getOrderByOrderNumber(string $orderNumber): ?Order - { - return $this->orderRepository->findOneBy(['number' => $orderNumber]); - } - - /** - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - */ - public function restoreFromOrder(Order $order): void - { - $this->sBasket->sDeleteBasket(); - $orderDetails = $order->getDetails(); - foreach ($orderDetails as $orderDetail) { - $this->processOrderDetail($order, $orderDetail); - } - - $this->events->notify(Event::BASKET_RESTORE_FROM_ORDER, [ - 'order' => $order, - ]); - - $this->sBasket->sRefreshBasket(); - } - - /** - * @throws ORMException - * @throws OptimisticLockException - */ - public function cancelOrder(Order $order): void - { - /** @var Status $statusCanceled */ - $statusCanceled = $this->statusRepository->find(Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED); - - $order->setPaymentStatus($statusCanceled); - - $this->modelManager->persist($order); - $this->modelManager->flush($order); - } - - /** - * @throws Enlight_Event_Exception - */ - private function processOrderDetail(Order $order, Detail $orderDetail): void - { - $orderDetailFiltered = $this->events->filter(Event::BASKET_BEFORE_PROCESS_ORDER_DETAIL, $orderDetail, [ - 'order' => $order, - 'orderDetail' => $orderDetail, - ]); - - if (!$orderDetailFiltered instanceof Detail) { - $this->events->notify(Event::BASKET_STOPPED_PROCESS_ORDER_DETAIL, [ - 'order' => $order, - 'orderDetail' => $orderDetailFiltered, - 'originalOrderDetail' => $orderDetail, - ]); - - return; - } - - switch ($orderDetailFiltered->getMode()) { - case self::MODE_PRODUCT: - case self::MODE_SURCHARGE_DISCOUNT: - $this->addArticle($orderDetailFiltered); - - break; - case self::MODE_PREMIUM_PRODUCT: - $this->addPremium($orderDetailFiltered); - - break; - case self::MODE_VOUCHER: - $this->addVoucher($orderDetailFiltered); - - break; - case self::MODE_REBATE: - break; - } - - $this->events->notify(Event::BASKET_AFTER_PROCESS_ORDER_DETAIL, [ - 'order' => $order, - 'orderDetail' => $orderDetailFiltered, - 'originalOrderDetail' => $orderDetail, - ]); - } - - /** - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - * @throws Zend_Db_Select_Exception - * @throws Zend_Db_Statement_Exception - */ - private function addArticle(Detail $orderDetail): void - { - // The order item doesn't have an article number or it isn't a regular Shopware article - // add it to the basket manually - if ('' !== $orderDetail->getArticleNumber() || !$this->isArticlesDetails($orderDetail->getArticleNumber())) { - $basketDetailId = $this->insertInToBasket($orderDetail); - $this->detailAttributesRestorer->restore($orderDetail->getId(), $basketDetailId); - - return; - } - - $basketDetailId = $this->sBasket->sAddArticle($orderDetail->getArticleNumber(), $orderDetail->getQuantity()); - $this->detailAttributesRestorer->restore($orderDetail->getId(), $basketDetailId); - } - - /** - * Searches in the s_articles_details table with the ordernumber column and returns true if an article is found. - * - * @throws Zend_Db_Select_Exception - * @throws Zend_Db_Statement_Exception - */ - private function isArticlesDetails(string $articleDetailNumber): bool - { - $result = $this->db->select() - ->from('s_articles_details') - ->where('ordernumber=?', $articleDetailNumber) - ->query() - ->fetch(); - - return !empty($result); - } - - /** - * Inserts data from a order detail row into a basket detail and returns the inserted ID. - * - * @throws Zend_Db_Adapter_Exception - */ - private function insertInToBasket(Detail $optionData): int - { - $this->db->insert('s_order_basket', [ - 'sessionID' => $this->session->get('sessionId'), - 'userID' => $this->session->get('sUserId') ?? 0, - 'articlename' => $optionData->getArticleName(), - 'ordernumber' => $optionData->getArticleNumber(), - 'articleID' => $optionData->getArticleId(), - 'quantity' => $optionData->getQuantity(), - 'price' => $optionData->getPrice(), - 'netprice' => null === $optionData->getPrice() - ? 0 - : $optionData->getPrice() / (1 + ($optionData->getTaxRate() / 100)), - 'tax_rate' => $optionData->getTaxRate(), - 'modus' => $optionData->getMode(), - 'esdarticle' => $optionData->getEsdArticle(), - 'config' => $optionData->getConfig(), - 'datum' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'), - 'currencyFactor' => Shopware()->Shop()->getCurrency()->getFactor(), - ]); - - return (int) $this->db->lastInsertId(); - } - - /** - * @throws Zend_Db_Adapter_Exception - */ - private function addPremium(Detail $orderDetail): void - { - Shopware()->Front()->Request()->setQuery('sAddPremium', $orderDetail->getArticleNumber()); - $this->sBasket->sInsertPremium(); - } - - /** - * @throws ORMException - * @throws OptimisticLockException - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - */ - private function addVoucher(Detail $orderDetail): void - { - /** @var Voucher $voucher */ - $voucher = $this->voucherRepository->findOneBy(['orderCode' => $orderDetail->getArticleNumber()]); - - if (!$voucher) { - return; - } - - $voucherCode = $voucher->getVoucherCode(); - - if (1 === $voucher->getModus()) { - /** @var Code $voucherCodeObject */ - $voucherCodeObject = $this->voucherCodeRepository->findOneBy([ - 'voucherId' => $voucher->getId(), - 'id' => $orderDetail->getArticleId(), - ]); - if ($voucherCodeObject) { - $voucherCode = $voucherCodeObject->getCode(); - $voucherCodeObject->setCustomerId(null); - $voucherCodeObject->setCashed(0); - $this->modelManager->persist($voucherCodeObject); - } - } - $this->modelManager->remove($orderDetail); - $this->modelManager->flush(); - - $this->sBasket->sAddVoucher( - $voucherCode - ); - } -} diff --git a/Components/Builder/NotificationBuilder.php b/Components/Builder/NotificationBuilder.php deleted file mode 100755 index cb83737e..00000000 --- a/Components/Builder/NotificationBuilder.php +++ /dev/null @@ -1,126 +0,0 @@ -modelManager = $modelManager; - $this->orderRepository = $modelManager->getRepository(Order::class); - $this->currency = new Currency(); - } - - /** - * Builds Notification object from Adyen webhook params. - * - * @param $params - * - * @throws OrderNotFoundException - * @throws InvalidParameterException - */ - public function fromParams($params): Notification - { - $notification = new Notification(); - - $notification->setStatus(NotificationStatus::STATUS_RECEIVED); - - if (!isset($params['merchantReference'])) { - throw InvalidParameterException::missingParameter('merchantReference'); - } - - /** @var Order $order */ - $order = $this->orderRepository->findOneBy(['number' => $params['merchantReference']]); - if (!$order) { - throw new OrderNotFoundException($params['merchantReference']); - } - - $notification->setOrder($order); - $notification->setOrderId($order->getId()); - - if (isset($params['pspReference'])) { - $notification->setPspReference($params['pspReference']); - } - if (isset($params['eventCode'])) { - $notification->setEventCode($params['eventCode']); - } - - if (isset($params['paymentMethod'])) { - $notification->setPaymentMethod($params['paymentMethod']); - } - - if (!isset($params['paymentMethod']) && isset($params['additionalData']['paymentMethodVariant'])) { - $notification->setPaymentMethod($params['additionalData']['paymentMethodVariant']); - } - - if (isset($params['success'])) { - $notification->setSuccess('true' === $params['success']); - } - if (isset($params['merchantAccountCode'])) { - $notification->setMerchantAccountCode($params['merchantAccountCode']); - } - if (isset($params['amount']['value'], $params['amount']['currency'])) { - $notification->setAmountValue($params['amount']['value']); - $notification->setAmountCurrency($params['amount']['currency']); - } - if (isset($params['reason'])) { - $notification->setErrorDetails($params['reason']); - } - - if (isset($params['eventCode'], $params['success'])) { - $notification->setScheduledProcessingTime($this->getProcessingTime($notification)); - } - - return $notification; - } - - /** - * Set delay in processing time for certain notifications. - */ - private function getProcessingTime(Notification $notification): \DateTime - { - $processingTime = new \DateTime(); - switch ($notification->getEventCode()) { - case 'AUTHORISATION': - if (!$notification->isSuccess()) { - $processingTime = $processingTime->add(new \DateInterval('PT30M')); - } - - break; - case 'OFFER_CLOSED': - $processingTime = $processingTime->add(new \DateInterval('PT30M')); - - break; - default: - break; - } - - return $processingTime; - } -} diff --git a/Components/Calculator/PriceCalculationService.php b/Components/Calculator/PriceCalculationService.php deleted file mode 100644 index b399c5e1..00000000 --- a/Components/Calculator/PriceCalculationService.php +++ /dev/null @@ -1,21 +0,0 @@ -getAmountExcludingTax($amount, $tax), 2); - } -} diff --git a/Components/CheckoutConfigProvider.php b/Components/CheckoutConfigProvider.php new file mode 100644 index 00000000..a50179cb --- /dev/null +++ b/Components/CheckoutConfigProvider.php @@ -0,0 +1,250 @@ + + */ + private $checkoutConfigCache = []; + + /** + * @var Enlight_Components_Session_Namespace + */ + private $session; + + public function __construct(Enlight_Components_Session_Namespace $session) + { + $this->session = $session; + } + + /** + * @return Response + * @throws InvalidCurrencyCode + */ + public function getCheckoutConfig(?Amount $forceAmount = null): Response + { + $request = $this->buildConfigRequest($forceAmount); + + $response = $this->getCheckoutConfigResponse($request, false, static function (PaymentCheckoutConfigRequest $request) { + return CheckoutAPI::get() + ->checkoutConfig(Shopware()->Shop()->getId()) + ->getPaymentCheckoutConfig($request); + }); + + if (!$response->isSuccessful()) { + return $response; + } + + $userData = Shopware()->Modules()->Admin()->sGetUserData(); + if ( + !empty($userData['additional']['user']['accountmode']) && + (int)$userData['additional']['user']['accountmode'] === Customer::ACCOUNT_MODE_FAST_LOGIN + ) { + $this->disableCardsSingleClickPayment($response); + } + + return $response; + } + + /** + * @param Amount|null $forceAmount + * @return Response + * @throws InvalidCurrencyCode + */ + public function getExpressCheckoutConfig(Amount $forceAmount): Response + { + $request = $this->buildConfigRequest($forceAmount); + + return $this->getCheckoutConfigResponse($request, true, static function (PaymentCheckoutConfigRequest $request) { + return CheckoutAPI::get() + ->checkoutConfig(Shopware()->Shop()->getId()) + ->getExpressPaymentCheckoutConfig($request); + }); + } + + /** + * @param Amount|null $forceAmount + * @return PaymentCheckoutConfigRequest + * @throws InvalidCurrencyCode + */ + private function buildConfigRequest(?Amount $forceAmount = null): PaymentCheckoutConfigRequest + { + $country = null; + if ($this->getUser() && isset($this->getUser()['additional']['country']['countryiso'])) { + $country = Country::fromIsoCode($this->getUser()['additional']['country']['countryiso']); + } + + if ( + !$country && + Shopware()->Modules() && + ($sAdmin = Shopware()->Modules()->Admin()) && + ($userData = $sAdmin->sGetUserData()) && + isset($userData['additional']['country']['countryiso']) + ) { + $country = Country::fromIsoCode($userData['additional']['country']['countryiso']); + } + + $shop = Shopware()->Shop(); + $userId = (int)$this->session->offsetGet('sUserId'); + $shopperReference = ($userId !== 0) ? $shop->getHost() . '_' . $shop->getId() . '_' . $userId : null; + + return new PaymentCheckoutConfigRequest( + $this->getAmount($forceAmount), + $country, + Shopware()->Shop()->getLocale()->getLocale(), + $shopperReference + ); + } + + /** + * Gets the response from cache or makes the response and cache the result by calling $responseCallback + * + * @param PaymentCheckoutConfigRequest $request + * @param callable $responseCallback + * @return Response|PaymentCheckoutConfigResponse + */ + private function getCheckoutConfigResponse( + PaymentCheckoutConfigRequest $request, + bool $isExpressCheckout, + callable $responseCallback + ): Response { + $cacheKey = implode('-', [ + $request->getShopperLocale(), + $request->getAmount()->getValue(), + (string)$request->getAmount()->getCurrency(), + (string)$request->getCountry(), + $request->getShopperReference(), + $isExpressCheckout ? 'express' : 'standard' + ]); + + if (array_key_exists($cacheKey, $this->checkoutConfigCache)) { + return $this->checkoutConfigCache[$cacheKey]; + } + + $configResponse = $responseCallback($request); + + if (!$configResponse->isSuccessful()) { + return $configResponse; + } + + $this->checkoutConfigCache[$cacheKey] = $configResponse; + + return $this->checkoutConfigCache[$cacheKey]; + } + + /** + * @param Amount|null $forceAmount + * @return Amount + * @throws InvalidCurrencyCode + */ + private function getAmount(?Amount $forceAmount = null): Amount + { + if ($forceAmount) { + return $forceAmount; + } + + $currencyName = $this->getBasket()['sCurrencyName'] ?? null; + if (!$currencyName && Shopware()->Shop() && Shopware()->Shop()->getCurrency()) { + $currencyName = Shopware()->Shop()->getCurrency()->getCurrency(); + } + + $cartAmount = $this->getBasketAmount(); + if (!$cartAmount && Shopware()->Modules()->Basket()) { + $cartAmount = (float)Shopware()->Modules()->Basket()->sGetAmount()['totalAmount']; + } + + return Amount::fromFloat( + $cartAmount, + Currency::fromIsoCode($currencyName ?? 'EUR') + ); + } + + /** + * Returns the full user data as array. + * + * @return array|null + */ + private function getUser(): ?array + { + if (!empty($this->session->sOrderVariables['sUserData'])) { + return $this->session->sOrderVariables['sUserData']; + } + + return null; + } + + /** + * Returns the full basket data as array. + * + * @return array|null + */ + private function getBasket(): ?array + { + if (!empty($this->session->sOrderVariables['sBasket'])) { + return $this->session->sOrderVariables['sBasket']; + } + + return null; + } + + /** + * Return the full amount to pay. + * + * @return float|null + */ + private function getBasketAmount(): ?float + { + $user = $this->getUser(); + $basket = $this->getBasket(); + if (!empty($user['additional']['charge_vat'])) { + return empty($basket['AmountWithTaxNumeric']) ? $basket['AmountNumeric'] : $basket['AmountWithTaxNumeric']; + } + + return $basket['AmountNetNumeric']; + } + + private function disableCardsSingleClickPayment(PaymentCheckoutConfigResponse $response): void + { + foreach ($response->getPaymentMethodsConfiguration() as $method) { + if (PaymentMethodCode::scheme()->equals($method->getCode())) { + /** @var CardConfig $additionalData */ + $additionalData = $method->getAdditionalData(); + $method->setAdditionalData(new CardConfig( + $additionalData->isShowLogos(), + false, + $additionalData->isInstallments(), + $additionalData->isInstallmentAmounts(), + $additionalData->isSendBasket(), + $additionalData->getInstallmentCountries(), + $additionalData->getMinimumAmount(), + $additionalData->getNumberOfInstallments() + )); + + return; + } + } + } +} diff --git a/Components/CompilerPass/NotificationProcessorCompilerPass.php b/Components/CompilerPass/NotificationProcessorCompilerPass.php deleted file mode 100644 index 358cb4bd..00000000 --- a/Components/CompilerPass/NotificationProcessorCompilerPass.php +++ /dev/null @@ -1,25 +0,0 @@ -getDefinition('AdyenPayment\Components\NotificationProcessor'); - $taggedServices = $container->findTaggedServiceIds('adyen.payment.notificationprocessor'); - - foreach ($taggedServices as $id => $tags) { - $definition->addMethodCall('addProcessor', [new Reference($id)]); - } - } -} diff --git a/Components/Configuration.php b/Components/Configuration.php deleted file mode 100755 index f6726efd..00000000 --- a/Components/Configuration.php +++ /dev/null @@ -1,177 +0,0 @@ -cachedConfigReader = $cachedConfigReader; - $this->connection = $connection; - } - - /** - * @param false|Shop $shop - */ - public function getEnvironment($shop = false): string - { - return self::ENV_LIVE === mb_strtoupper($this->getConfig('environment', $shop)) - ? Environment::LIVE - : Environment::TEST; - } - - /** - * @param bool $shop - */ - public function isTestMode($shop = false): bool - { - return Environment::TEST === $this->getEnvironment($shop); - } - - /** - * @param false|Shop $shop - */ - public function getMerchantAccount($shop = false): string - { - return (string) $this->getConfig('merchant_account', $shop); - } - - /** - * @param false|Shop $shop - */ - public function getConfig(?string $key = null, $shop = false) - { - if (!$shop) { - try { - $shop = Shopware()->Shop(); - } catch (ServiceNotFoundException $exception) { - //The Shop service is not available in the context (i.e. getting the config from the Backend) - $shop = null; - } - } - - $config = $this->cachedConfigReader->getByPluginName(AdyenPayment::NAME, $shop); - if (null === $key) { - return $config; - } - - if (array_key_exists($key, $config)) { - return $config[$key]; - } - } - - /** - * @param false|Shop $shop - */ - public function getApiKey($shop = false): string - { - return (string) $this->getConfig( - 'api_key_'.$this->getEnvironment($shop), - $shop - ); - } - - /** - * @param false|Shop $shop - */ - public function getApiUrlPrefix($shop = false): string - { - return (string) $this->getConfig('api_url_prefix', $shop); - } - - public function getClientKey(Shop $shop): string - { - return (string) $this->getConfig('client_key_'.$this->getEnvironment($shop), $shop); - } - - /** - * @param bool|Shop $shop - */ - public function getNotificationHmac($shop = false): string - { - return (string) $this->getConfig( - 'notification_hmac_'.$this->getEnvironment($shop), - $shop - ); - } - - /** - * @param bool $shop - */ - public function getNotificationAuthUsername($shop = false): string - { - return (string) $this->getConfig( - 'notification_auth_username_'.$this->getEnvironment($shop), - $shop - ); - } - - /** - * @param bool $shop - */ - public function getNotificationAuthPassword($shop = false): string - { - return (string) $this->getConfig( - 'notification_auth_password_'.$this->getEnvironment($shop), - $shop - ); - } - - /** - * @param bool $shop - */ - public function getGoogleMerchantId($shop = false): string - { - return (string) $this->getConfig('google_merchant_id', $shop); - } - - /** - * @param bool $shop - */ - public function isPaymentmethodsCacheEnabled($shop = false): bool - { - return (bool) $this->getConfig('paymentmethods_cache', $shop); - } - - /** - * @param bool $shop - */ - public function getManualReviewRejectAction($shop = false): string - { - return (string) $this->getConfig('manual_review_rejected_action', $shop); - } - - /** - * @throws \Doctrine\DBAL\DBALException - */ - public function getCurrentPluginVersion(): int - { - $sql = 'SELECT version FROM s_core_plugins WHERE plugin_name = ? ORDER BY version DESC'; - $stmt = $this->connection->prepare($sql); - $stmt->execute([AdyenPayment::NAME]); - - return (int) $stmt->fetchColumn(); - } -} diff --git a/Components/Configuration/ConfigurationService.php b/Components/Configuration/ConfigurationService.php new file mode 100644 index 00000000..92e4c1c8 --- /dev/null +++ b/Components/Configuration/ConfigurationService.php @@ -0,0 +1,67 @@ + $guid]; + if ($this->isAutoTestMode()) { + $params['auto-test'] = 1; + } + + return Url::getFrontUrl('AdyenAsyncProcess', 'run', $params); + } + + /** + * @return string + */ + public function getIntegrationVersion(): string + { + /** @var ShopwareVersionCheck $versionCheck */ + $versionCheck = Shopware()->Container()->get('adyen_payment.components.shopware_version_check'); + + return $versionCheck->getShopwareVersion(); + } + + /** + * @return string + */ + public function getIntegrationName(): string + { + return self::INTEGRATION_NAME; + } + + /** + * @return string + */ + public function getPluginName(): string + { + return 'AdyenPayment'; + } + + /** + * @return string + */ + public function getPluginVersion(): string + { + return Plugin::getVersion(); + } +} diff --git a/Components/ConfigurationInterface.php b/Components/ConfigurationInterface.php deleted file mode 100644 index e7841e66..00000000 --- a/Components/ConfigurationInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -session = $session; + } + + public function hasMessages(): bool + { + return (bool)$this->session->get(self::ERROR_MESSAGES_SESSION_KEY) || + (bool)$this->session->get(self::SUCCESS_MESSAGES_SESSION_KEY); + } + + public function add(string ...$messages): void + { + $this->session->offsetSet( + self::ERROR_MESSAGES_SESSION_KEY, + array_merge( + array_values($this->read()), + array_values($messages) + ) + ); + } + + public function addSuccessMessage(string ...$messages): void + { + $this->session->offsetSet( + self::SUCCESS_MESSAGES_SESSION_KEY, + array_merge( + [$this->readSuccessMessages()], + array_values($messages) + ) + ); + } + + public function read(): array + { + $messages = (array)($this->session->offsetGet(self::ERROR_MESSAGES_SESSION_KEY) ?? []); + $this->session->offsetUnset(self::ERROR_MESSAGES_SESSION_KEY); + + return $messages; + } + + public function readSuccessMessages(): string + { + $messages = ($this->session->offsetGet(self::SUCCESS_MESSAGES_SESSION_KEY)); + $this->session->offsetUnset(self::SUCCESS_MESSAGES_SESSION_KEY); + + return !empty($messages) ? $messages[0] : ''; + } +} diff --git a/Components/FifoNotificationLoader.php b/Components/FifoNotificationLoader.php deleted file mode 100755 index b9c4826c..00000000 --- a/Components/FifoNotificationLoader.php +++ /dev/null @@ -1,40 +0,0 @@ -notificationManager = $notificationManager; - } - - public function load(int $amount): \Generator - { - try { - yield $this->notificationManager->getNextNotificationToHandle(); - if ($amount > 1) { - yield from $this->load($amount - 1); - } - } catch (NoResultException $exception) { - return; - } catch (NonUniqueResultException $exception) { - return; - } - } -} diff --git a/Components/FifoTextNotificationLoader.php b/Components/FifoTextNotificationLoader.php deleted file mode 100755 index cce21c33..00000000 --- a/Components/FifoTextNotificationLoader.php +++ /dev/null @@ -1,28 +0,0 @@ -textNotificationManager = $textNotificationManager; - } - - public function get(): array - { - return $this->textNotificationManager->getTextNextNotificationsToHandle(); - } -} diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php deleted file mode 100755 index 07da0055..00000000 --- a/Components/IncomingNotificationManager.php +++ /dev/null @@ -1,127 +0,0 @@ -logger = $logger; - $this->notificationBuilder = $notificationBuilder; - $this->entityManager = $entityManager; - $this->notificationManager = $notificationManager; - } - - /** - * @param TextNotification[] $textNotifications - * - * @throws ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function convertNotifications(array $textNotifications): void - { - foreach ($textNotifications as $textNotificationItem) { - try { - if (!empty($textNotificationItem->getTextNotification())) { - $notification = $this->notificationBuilder->fromParams( - json_decode($textNotificationItem->getTextNotification(), true) - ); - - $this->notificationManager->guardDuplicate($notification); - - $this->entityManager->persist($notification); - } - } catch (InvalidParameterException $exception) { - $this->logger->warning( - $exception->getMessage().' '.$textNotificationItem->getTextNotification() - ); - } catch (OrderNotFoundException $exception) { - $this->logger->warning( - $exception->getMessage().' '.$textNotificationItem->getTextNotification() - ); - } catch (DuplicateNotificationException $exception) { - $this->logger->notice( - $exception->getMessage() - ); - } - $this->entityManager->remove($textNotificationItem); - $this->entityManager->flush(); - } - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function saveTextNotification(array $textNotificationItems): \Generator - { - foreach ($textNotificationItems as $textNotificationItem) { - try { - if (!empty($textNotificationItem['NotificationRequestItem'])) { - if ($this->skipNotification($textNotificationItem['NotificationRequestItem'])) { - $this->logger->info( - 'Skipped notification', - ['eventCode' => $textNotificationItem['eventCode'] ?? ''] - ); - - continue; - } - - $textNotification = new TextNotification(); - $textNotification->setTextNotification( - json_encode($textNotificationItem['NotificationRequestItem']) - ); - $this->entityManager->persist($textNotification); - } - } catch (ORMException $exception) { - $this->logger->warning($exception->getMessage()); - yield new TextNotificationItemFeedback($exception->getMessage(), $textNotificationItem); - } - } - $this->entityManager->flush(); - } - - private function skipNotification(array $notificationRequest): bool - { - if (!empty($notificationRequest['eventCode']) && - false !== mb_strpos($notificationRequest['eventCode'], 'REPORT_')) { - return true; - } - - return false; - } -} diff --git a/Components/Integration/FileService.php b/Components/Integration/FileService.php new file mode 100644 index 00000000..a9259934 --- /dev/null +++ b/Components/Integration/FileService.php @@ -0,0 +1,80 @@ +mediaService = $mediaService; + } + + /** + * @param string $fileContent + * @param string $name + * + * @return void + */ + public function write(string $fileContent, string $name): void + { + $path = 'media/image/' . $name . '.png'; + + if ($this->mediaService->has($path)) { + $this->delete($path); + } + $this->mediaService->write($path, $fileContent); + } + + /** + * @param string $name + * + * @return false|string + */ + public function read(string $name) + { + return $this->mediaService->read('media/image/' . $name . '.png'); + } + + /** + * @param string $name + * + * @return string|null + */ + public function getLogoUrl(string $name): ?string + { + if ($this->mediaService->has('media/image/' . $name . '.png')) { + return $this->mediaService->getUrl('media/image/' . $name . '.png'); + } + + return ''; + } + + /** + * @param string $name + * + * @return void + */ + public function delete(string $name): void + { + if (!$this->mediaService->has('media/image/' . $name . '.png')) { + return; + } + + $this->mediaService->delete('media/image/' . $name . '.png'); + } +} diff --git a/Components/Integration/LegacyMerchantReferenceNormalizationWebhookHandler.php b/Components/Integration/LegacyMerchantReferenceNormalizationWebhookHandler.php new file mode 100644 index 00000000..62f6f99b --- /dev/null +++ b/Components/Integration/LegacyMerchantReferenceNormalizationWebhookHandler.php @@ -0,0 +1,56 @@ +orderRepository = $orderRepository; + + parent::__construct($synchronizationService, $queueService); + } + + public function handle(Webhook $webhook): void + { + $legacyOrderMap = $this->orderRepository->getOrdersByNumbers([$webhook->getMerchantReference()]); + if (array_key_exists($webhook->getMerchantReference(), $legacyOrderMap)) { + $webhook = new Webhook( + $webhook->getAmount(), + $webhook->getEventCode(), + $webhook->getEventDate(), + $webhook->getHmacSignature(), + $webhook->getMerchantAccountCode(), + $legacyOrderMap[$webhook->getMerchantReference()]->getTemporaryId(), + $webhook->getPspReference(), + $webhook->getPaymentMethod(), + $webhook->getReason(), + $webhook->isSuccess(), + $webhook->getOriginalReference(), + $webhook->getRiskScore(), + $webhook->isLive() + ); + } + + parent::handle($webhook); + } +} diff --git a/Components/Integration/OrderService.php b/Components/Integration/OrderService.php new file mode 100644 index 00000000..6d5611f3 --- /dev/null +++ b/Components/Integration/OrderService.php @@ -0,0 +1,108 @@ +orderRepository = $orderRepository; + $this->modules = $modules; + } + + /** + * @inheritDoc + */ + public function orderExists(string $merchantReference): bool + { + $order = $this->orderRepository->getOrderByTemporaryId($merchantReference); + + return !empty($order) && $order->getShop()->getId() === (int)StoreContext::getInstance()->getStoreId(); + } + + /** + * @inheritDoc + */ + public function updateOrderStatus(Webhook $webhook, string $statusId): void + { + $order = $this->orderRepository->getOrderByTemporaryId($webhook->getMerchantReference()); + + if (!$order) { + return; + } + + /** @var sOrder $sOrder */ + $sOrder = $this->modules->getModule('sOrder'); + $sOrder->setPaymentStatus($order->getId(), $statusId); + + if ($order->getTransactionId() === $order->getTemporaryId()) { + $originalReference = $webhook->getPspReference(); + $order->setTransactionId($originalReference); + $this->orderRepository->updateOrder($order); + } + } + + /** + * @param string $merchantReference + * + * @return string + */ + public function getOrderCurrency(string $merchantReference): string + { + $order = $this->orderRepository->getOrderByTemporaryId($merchantReference); + + return $order ? $order->getCurrency() : ''; + } + + /** + * @param string $merchantReference + * + * @return string + */ + public function getOrderUrl(string $merchantReference): string + { + $order = $this->orderRepository->getOrderByTemporaryId($merchantReference); + + return $order ? $this->getOrderUrlForId((int)$order->getId()) : 'javascript:'; + } + + public function getOrderUrlForId(int $orderId): string + { + return implode('', [ + 'javascript:' . + 'postMessageApi.openModule({' . + "name: 'Shopware.apps.Order', " . + "action: 'detail', " . + "params: {" . + "orderId: {$orderId}" . + "}" . + "}) && undefined;" + ]); + } +} diff --git a/Components/Integration/PaymentMethodService.php b/Components/Integration/PaymentMethodService.php new file mode 100644 index 00000000..2aec1b21 --- /dev/null +++ b/Components/Integration/PaymentMethodService.php @@ -0,0 +1,610 @@ +storeContext = $storeContext; + $this->entityManager = $entityManager; + $this->storeRepository = $storeRepository; + $this->paymentInstaller = $paymentInstaller; + $this->fileService = $fileService; + $this->paymentRepository = $paymentRepository; + $this->storeService = $storeService; + } + + /** + * Creates new payment method in Shopware. + * + * @param PaymentMethod $method + * + * @return void + * + * @throws ORMException + * @throws OptimisticLockException + * @throws StoreDoesNotExistException + */ + public function createPaymentMethod(PaymentMethod $method): void + { + $store = $this->getCurrentStore(); + + $this->savePaymentMean($method, $store); + } + + /** + * @inheritDoc + * + * @throws ORMException + * @throws OptimisticLockException + * @throws PaymentMeanDoesNotExistException + * @throws StoreDoesNotExistException + * @throws Exception + */ + public function updatePaymentMethod(PaymentMethod $method): void + { + $store = $this->getCurrentStore(); + $payment = $this->getPaymentMeanByName(self::ADYEN_NAME_PREFIX . $method->getCode()); + + if (!$payment && $method->getCode() !== (string)PaymentMethodCode::oney()) { + throw new PaymentMeanDoesNotExistException( + 'Payment mean with name ' . self::ADYEN_NAME_PREFIX + . $method->getCode() . ' does not exist.' + ); + } + + $this->savePaymentMean($method, $store); + } + + /** + * @inheritDoc + * + * @throws BaseException + * @throws Exception + */ + public function deletePaymentMethod(string $methodId): void + { + $method = $this->getPaymentService()->getPaymentMethodById($methodId); + + if ($method === null) { + throw new BaseException('Payment method with id ' . $methodId . 'does not exist.'); + } + + if ($method->getCode() === (string)PaymentMethodCode::oney()) { + $this->removeOneyMethods($method); + + return; + } + + $this->disablePaymentMean(self::ADYEN_NAME_PREFIX . $method->getCode()); + } + + /** + * @return void + * + * @throws OptimisticLockException + * @throws \Doctrine\ORM\Exception\ORMException + * @throws Exception + */ + public function deleteAllPaymentMethods(): void + { + $paymentMeans = $this->getConfiguredPaymentMeans(); + + if (empty($paymentMeans)) { + return; + } + + foreach ($paymentMeans as $paymentMean) { + $this->disableMean($paymentMean); + } + + $this->entityManager->flush(); + } + + /** + * @return void + * + * @throws OptimisticLockException + * @throws StoreDoesNotExistException + * @throws \Doctrine\ORM\Exception\ORMException + * @throws Exception + */ + public function deletePaymentMethodsForAllStores(): void + { + $paymentMeans = $this->getConfiguredPaymentMeans(); + + if (empty($paymentMeans)) { + return; + } + + foreach ($paymentMeans as $paymentMean) { + $this->deleteMean($paymentMean); + } + + $this->entityManager->flush(); + } + + /** + * @return void + * + * @throws OptimisticLockException + * @throws \Doctrine\ORM\Exception\ORMException + * @throws Exception + */ + public function enableAllPaymentMethods(): void + { + $paymentMeans = $this->getConfiguredPaymentMeans(); + + if (empty($paymentMeans)) { + return; + } + + foreach ($paymentMeans as $paymentMean) { + $paymentMean->setHide(false); + $paymentMean->setActive(true); + $this->entityManager->persist($paymentMean); + } + + $this->entityManager->flush(); + } + + /** + * @return array|Payment[] + * + * @throws Exception + */ + private function getConfiguredPaymentMeans(): array + { + $methods = $this->paymentRepository->getConfiguredPaymentMethodsForAllShops(); + + if (StoreContext::getInstance()->getStoreId()) { + $methods = $this->paymentRepository->getConfiguredPaymentMethods(); + } + + if (empty($methods)) { + return []; + } + + $names = []; + + foreach ($methods as $method) { + $names[] = self::ADYEN_NAME_PREFIX . $method->getCode(); + + if ($method->getCode() === (string)PaymentMethodCode::oney()) { + /** @var Oney $additionalData */ + $additionalData = $method->getAdditionalData(); + $installments = $additionalData->getSupportedInstallments(); + + foreach ($installments as $installment) { + $names[] = self::ADYEN_NAME_PREFIX . 'facilypay_' . $installment . 'x'; + } + } + } + + return $this->getPaymentMeansByName($names); + } + + /** + * @param string $name + * + * @return Payment|null + */ + private function getPaymentMeanByName(string $name): ?Payment + { + $repository = Shopware()->Models()->getRepository(Payment::class); + $query = $repository->createQueryBuilder('paymentmeans'); + $query->where('paymentmeans.name = :name') + ->setParameter('name', $name); + + $paymentMeans = $query->getQuery()->getResult(); + + return $paymentMeans[0] ?? null; + } + + /** + * @param array $names + * + * @return Payment[] + */ + private function getPaymentMeansByName(array $names): array + { + $repository = Shopware()->Models()->getRepository(Payment::class); + $query = $repository->createQueryBuilder('paymentmeans'); + $query->where('paymentmeans.name in (:names)') + ->setParameter('names', $names, Connection::PARAM_STR_ARRAY); + + return $query->getQuery()->getResult(); + } + + /** + * @param PaymentMethod $method + * @param ShopwareStore $store + * + * @return void + * + * @throws ORMException + * @throws OptimisticLockException + * @throws Exception + */ + private function savePaymentMean(PaymentMethod $method, ShopwareStore $store): void + { + if ($method->getCode() === (string)PaymentMethodCode::oney()) { + $this->saveOneyMeans($method, $store); + + return; + } + + $payment = $this->getPaymentMeanByName(self::ADYEN_NAME_PREFIX . $method->getCode()); + + $shops = array_merge([$store], $this->storeRepository->getShopwareLanguageShops([$store->getId()])); + + if ($payment) { + /** @var ShopwareStore $enabledShop */ + foreach ($payment->getShops()->toArray() as $enabledShop) { + foreach ($shops as $shop) { + if ($shop->getId() === $enabledShop->getId()) { + continue 2; + } + } + + $shops[] = $enabledShop; + } + } + + $this->paymentInstaller->createOrUpdate( + AdyenPayment::NAME, + [ + 'name' => self::ADYEN_NAME_PREFIX . $method->getCode(), + 'description' => $method->getName(), + 'additionalDescription' => $method->getDescription(), + 'active' => true, + 'esdActive' => true, + 'hide' => false, + 'position' => $this->getPosition($method), + 'action' => 'AdyenPaymentProcess', + 'source' => AdyenPayment::PAYMENT_METHOD_SOURCE, + 'debitPercent' => $method->getPercentSurcharge() ?? 0, + 'surcharge' => $method->getFixedSurcharge(), + 'countries' => $this->getCountries(), + 'shops' => new ArrayCollection($shops), + ] + ); + } + + /** + * @param PaymentMethod $method + * @param ShopwareStore $store + * + * @return void + * + * @throws Exception + */ + private function saveOneyMeans(PaymentMethod $method, ShopwareStore $store): void + { + /** @var Oney $additionalData */ + $additionalData = $method->getAdditionalData(); + $installments = $additionalData->getSupportedInstallments(); + + foreach ($installments as $installment) { + $payment = $this->getPaymentMeanByName(self::ADYEN_NAME_PREFIX . 'facilypay_' . $installment . 'x'); + + $shops = array_merge([$store], $this->storeRepository->getShopwareLanguageShops([$store->getId()])); + + if ($payment) { + /** @var ShopwareStore $enabledShop */ + foreach ($payment->getShops()->toArray() as $enabledShop) { + foreach ($shops as $shop) { + if ($shop->getId() === $enabledShop->getId()) { + continue 2; + } + } + + $shops[] = $enabledShop; + } + } + + $this->paymentInstaller->createOrUpdate( + AdyenPayment::NAME, + [ + 'name' => self::ADYEN_NAME_PREFIX . 'facilypay_' . $installment . 'x', + 'description' => $method->getName() . ' ' . $installment . 'X', + 'additionalDescription' => $method->getDescription(), + 'active' => true, + 'esdActive' => true, + 'hide' => false, + 'position' => $this->getPosition($method), + 'action' => 'AdyenPaymentProcess', + 'source' => AdyenPayment::PAYMENT_METHOD_SOURCE, + 'debitPercent' => $method->getPercentSurcharge() ?? 0, + 'surcharge' => $method->getFixedSurcharge(), + 'countries' => $this->getCountries(), + 'shops' => new ArrayCollection($shops), + ] + ); + } + + $this->disableOneyInstallments($method); + } + + private function disableOneyInstallments(PaymentMethod $method) + { + $oneyMeans = $this->getOneyPaymentMeans(); + $enabledInstallments = $method->getAdditionalData()->getSupportedInstallments(); + + foreach ($oneyMeans as $mean) { + $installment = str_replace(self::ADYEN_NAME_PREFIX . 'facilypay_', '', $mean->getName()); + $installment = str_replace('x', '', $installment); + + if (!in_array($installment, $enabledInstallments)) { + $name = self::ADYEN_NAME_PREFIX . 'facilypay_' . $installment . 'x'; + + $this->disablePaymentMean($name); + } + } + } + + /** + * @return array + */ + private function getOneyPaymentMeans(): array + { + $repository = Shopware()->Models()->getRepository(Payment::class); + $query = $repository->createQueryBuilder('paymentmeans'); + $query->where('paymentmeans.name LIKE :name') + ->setParameter('name', '%' . self::ADYEN_NAME_PREFIX . 'facilypay_%'); + + $paymentMeans = $query->getQuery()->getResult(); + + return $paymentMeans ?? []; + } + + /** + * @param PaymentMethod $method + * + * @return void + * + * @throws StoreDoesNotExistException + * @throws \Doctrine\ORM\Exception\ORMException + */ + private function removeOneyMethods(PaymentMethod $method): void + { + /** @var Oney $additionalData */ + $additionalData = $method->getAdditionalData(); + $installments = $additionalData->getSupportedInstallments(); + $this->fileService->delete($method->getMethodId()); + + foreach ($installments as $installment) { + $name = self::ADYEN_NAME_PREFIX . 'facilypay_' . $installment . 'x'; + + $this->disablePaymentMean($name); + } + } + + /** + * @param $name + * + * @return void + * + * @throws StoreDoesNotExistException + * @throws \Doctrine\ORM\Exception\ORMException + */ + private function disablePaymentMean($name): void + { + $payment = $this->getPaymentMeanByName($name); + + if (!$payment) { + return; + } + + $this->disableMean($payment); + } + + /** + * @param Payment $paymentMean + * + * @return void + * + * @throws OptimisticLockException + * @throws StoreDoesNotExistException + * @throws \Doctrine\ORM\Exception\ORMException + */ + private function deleteMean(Payment $paymentMean) + { + $storesForRemoval = $this->getStoresForRemoval(); + $stores = $paymentMean->getShops(); + + foreach ($storesForRemoval as $store) { + $stores->removeElement($store); + } + + if ($stores->isEmpty()) { + $paymentMean->setActive(false); + } + + $paymentMean->setShops($stores); + $this->entityManager->persist($paymentMean); + $this->entityManager->flush(); + } + + /** + * @param Payment $paymentMean + * + * @return void + * + * @throws OptimisticLockException + * @throws \Doctrine\ORM\Exception\ORMException + */ + private function disableMean(Payment $paymentMean) + { + $stores = $paymentMean->getShops(); + + foreach ($stores as $store) { + if ($store->getId() . '' === StoreContext::getInstance()->getStoreId()) { + $stores->removeElement($store); + } + } + + if ($stores->isEmpty()) { + $paymentMean->setActive(false); + } + + $paymentMean->setShops($stores); + $this->entityManager->persist($paymentMean); + $this->entityManager->flush(); + } + + /** + * @return ArrayCollection + */ + private function getCountries(): ArrayCollection + { + $repository = Shopware()->Models()->getRepository(Country::class); + $queryBuilder = $repository->createQueryBuilder('country'); + + return new ArrayCollection($queryBuilder->getQuery()->getResult()); + } + + /** + * @param PaymentMethod $method + * + * @return int + * + * @throws Exception + */ + private function getPosition(PaymentMethod $method): int + { + $availableMethods = $this->getPaymentService()->getAvailableMethods(); + $position = 0; + + foreach ($availableMethods as $availableMethod) { + if ($availableMethod->getCode() === $method->getCode()) { + return $position; + } + + $position++; + } + + return 0; + } + + /** + * @return array + */ + private function getStoresForRemoval(): array + { + $stores = $this->storeService->getConnectedStores(); + $shopwareStores = []; + + foreach ($stores as $store){ + $shopwareStores[] = $this->storeRepository->getStoreById($store); + } + + return $shopwareStores; + } + + /** + * @return ShopwareStore + * + * @throws StoreDoesNotExistException + */ + private function getCurrentStore(): ShopwareStore + { + $store = $this->storeRepository->getStoreById($this->storeContext->getStoreId()); + + if (!$store) { + throw new StoreDoesNotExistException( + 'Store with id ' . $this->storeContext->getStoreId() + . ' does not exist.' + ); + } + + return $store; + } + + /** + * @return PaymentService + */ + private function getPaymentService(): PaymentService + { + return ServiceRegister::getService(PaymentService::class); + } +} diff --git a/Components/Integration/PaymentProcessors/AddressProcessor.php b/Components/Integration/PaymentProcessors/AddressProcessor.php new file mode 100644 index 00000000..317db9bf --- /dev/null +++ b/Components/Integration/PaymentProcessors/AddressProcessor.php @@ -0,0 +1,108 @@ +countryRepository = $countryRepository; + } + + public function process(PaymentRequestBuilder $builder, StartTransactionRequestContext $context): void + { + $billingAddressRawData = $context->getStateData()->get('billingAddress'); + $stateDataCountry = $context->getStateData()->get('countryCode'); + $deliveryAddressRawData = $context->getStateData()->get('deliveryAddress'); + + $userData = $context->getCheckoutSession()->get('user'); + + if (empty($userData)) { + return; + } + + if (!empty($userData['billingaddress'])) { + /** @var Country[] $country */ + $country = $this->countryRepository->getCountryQuery($userData['billingaddress']['countryId'])->getResult(); + + $this->setBillingAddress($billingAddressRawData, $country[0] ?: null, $userData, $builder); + $this->setCountryCode($stateDataCountry, $country[0] ?: null, $builder); + } + + if (!empty($userData['shippingaddress']) && empty($deliveryAddressRawData)) { + /** @var Country[] $country */ + $country = $this->countryRepository->getCountryQuery($userData['shippingaddress']['countryId'])->getResult(); + $state = null; + + if (!empty($userData['shippingaddress']['stateID'])) { + $state = Shopware()->Models()->getRepository('Shopware\Models\Country\State')->findOneBy(['id' => $userData['shippingaddress']['stateID']]); + } + + $countryIso = $country[0] ? $country[0]->getIso() : ''; + + $deliveryAddress = new DeliveryAddress( + $userData['shippingaddress']['city'] ?? '', + $countryIso, + '', + $userData['shippingaddress']['zipcode'] ?? '', + $state ? $state->getName() : $countryIso, + $userData['shippingaddress']['street'] ?? '' + ); + + $builder->setDeliveryAddress($deliveryAddress); + } + } + + private function setBillingAddress( + ?array $billingAddressRawData, + ?Country $country, + array $userData, + PaymentRequestBuilder $builder + ): void + { + if (!empty($billingAddressRawData)) { + return; + } + + $billingAddress = new BillingAddress( + $userData['billingaddress']['city'] ?? '', + $country ? $country->getIso() : '', + '', + $userData['billingaddress']['zipcode'] ?? '', + '', + $userData['billingaddress']['street'] ?? '' + ); + + $builder->setBillingAddress($billingAddress); + } + + private function setCountryCode(?array $stateDataCountry, ?Country $country, PaymentRequestBuilder $builder): void + { + if (!empty($stateDataCountry)) { + return; + } + + $builder->setCountryCode($country ? $country->getIso() : ''); + } +} diff --git a/Components/Integration/PaymentProcessors/BasketItemsProcessor.php b/Components/Integration/PaymentProcessors/BasketItemsProcessor.php new file mode 100644 index 00000000..2f4f91ab --- /dev/null +++ b/Components/Integration/PaymentProcessors/BasketItemsProcessor.php @@ -0,0 +1,94 @@ +generalSettingsService = $generalSettingsService; + $this->articleRepository = $articleRepository; + } + + /** + * @throws RepositoryOperationFailedException + */ + public function process(PaymentRequestBuilder $builder, StartTransactionRequestContext $context): void + { + $generalSettings = $this->generalSettingsService->getGeneralSettings(); + $basket = $context->getCheckoutSession()->get('basket'); + $user = $context->getCheckoutSession()->get('user'); + + $additionalData = new AdditionalData( + ($generalSettings && $generalSettings->isBasketItemSync()) + ? new RiskData($this->getItems($basket, $user)) : null); + + $builder->setAdditionalData($additionalData); + } + + + /** + * @param array $basket + * @param array $user + * + * @return BasketItem[] + */ + private function getItems(array $basket, array $user): array + { + $items = []; + $basketContent = $basket['content']; + + foreach ($basketContent as $item) { + /** @var Article[] $articles */ + $articles = $this->articleRepository->getArticleQuery($item['additional_details']['articleID'])->getResult(); + $article = $articles[0] ?? null; + + $items[] = new BasketItem( + $item['additional_details']['articleID'] ?? '', + '', + 0, + $article ? $article->getCategories()->first()->getName() : '', + '', + $basket['sCurrencyName'] ?? '', + '', + $item['articlename'] ?? '', + $item['quantity'] ?? 0, + $user['additional']['user']['email'] ?? '', + '', + '', + $item['additional_details']['ean'] ?? '' + ); + } + + return $items; + } +} diff --git a/Components/Integration/PaymentProcessors/BirthdayProcessor.php b/Components/Integration/PaymentProcessors/BirthdayProcessor.php new file mode 100644 index 00000000..133170ca --- /dev/null +++ b/Components/Integration/PaymentProcessors/BirthdayProcessor.php @@ -0,0 +1,32 @@ +getStateData()->get('dateOfBirth'); + + if (!empty($stateDataBirthday)) { + return; + } + + $user = $context->getCheckoutSession()->get('user'); + + if (empty($user) || !isset($user['additional']['user']['birthday'])) { + return; + } + + $builder->setDateOfBirth($user['additional']['user']['birthday']); + } +} diff --git a/Components/Integration/PaymentProcessors/L2L3DataProcessor.php b/Components/Integration/PaymentProcessors/L2L3DataProcessor.php new file mode 100644 index 00000000..e22cffe4 --- /dev/null +++ b/Components/Integration/PaymentProcessors/L2L3DataProcessor.php @@ -0,0 +1,133 @@ +paymentService = $paymentService; + $this->countryRepository = $countryRepository; + } + + /** + * @param PaymentRequestBuilder $builder + * @param StartTransactionRequestContext $context + * + * @return void + * + * @throws Exception + */ + public function process(PaymentRequestBuilder $builder, StartTransactionRequestContext $context): void + { + $basket = $context->getCheckoutSession()->get('basket'); + $user = $context->getCheckoutSession()->get('user'); + $country = $this->getCountryById($user['shippingaddress']['countryId']); + + if (!$this->shouldSyncL2L3Data((string)$context->getPaymentMethodCode())) { + return; + } + + $additionalData = new AdditionalData( + null, + new EnhancedSchemeData( + $basket['AmountNumeric'] - $basket['AmountNetNumeric'], + $user['additional']['user']['id'] ?? '', + $basket['sShippingcostsWithTax'] ?? '', + '', + (new \DateTime())->format('dMy'), + '', + $user['shippingaddress']['state'] ?? '', + $country ? $country->getIso() : '', + $user['shippingaddress']['zip'] ?? '', + $this->getDetails($basket['content'])) + ); + + $builder->setAdditionalData($additionalData); + } + + /** + * @param string $code + * + * @return bool + * + * @throws Exception + */ + private function shouldSyncL2L3Data(string $code): bool + { + $creditCardConfig = $this->paymentService->getPaymentMethodByCode($code); + + if ($creditCardConfig) { + return $creditCardConfig->getAdditionalData() !== null + && $creditCardConfig->getAdditionalData()->isSendBasket(); + } + + return false; + } + + /** + * @param array $basketContent + * + * @return ItemDetailLine[] + */ + private function getDetails(array $basketContent): array + { + $details = []; + + foreach ($basketContent as $item) { + $details[] = new ItemDetailLine( + $item['additional_details']['articleName'] ?? '', + $item['additional_details']['ean'] ?? '', + $item['quantity'] ?? 0, + '', + $item['additional_details']['price'] ?? 0, + '', + '', + '' + ); + } + + return $details; + } + + /** + * @param string $id + * + * @return Country|null + */ + private function getCountryById(string $id): ?Country + { + $country = $this->countryRepository->getCountryQuery($id)->getResult(); + + return $country[0] ?? null; + } +} diff --git a/Components/Integration/PaymentProcessors/LineItemsProcessor.php b/Components/Integration/PaymentProcessors/LineItemsProcessor.php new file mode 100644 index 00000000..307d7054 --- /dev/null +++ b/Components/Integration/PaymentProcessors/LineItemsProcessor.php @@ -0,0 +1,63 @@ +articleRepository = $articleRepository; + } + + public function process(PaymentRequestBuilder $builder, StartTransactionRequestContext $context): void + { + $basket = $context->getCheckoutSession()->get('basket'); + $basketContent = $basket['content']; + $lineItems = []; + + foreach ($basketContent as $item) { + $amountExcludingTax = $item['amountnetNumeric'] ? round($item['amountnetNumeric'], 2) : 0; + $taxPercentage = $item['tax_rate'] ?? 0; + $amountIncludingTax = $item['amountNumeric'] ?? 0; + $taxAmount = $amountIncludingTax - $amountExcludingTax; + /** @var Article[] $articles */ + $articles = $this->articleRepository->getArticleQuery($item['additional_details']['articleID'])->getResult(); + $article = $articles[0] ?? null; + + $lineItems[] = new LineItem( + $item['articleID'] ?? '', + $amountExcludingTax * 100, + $amountIncludingTax * 100, + $taxAmount * 100, + $taxPercentage * 100, + substr($item['additional_details']['description'] !== '' ? $item['additional_details']['description'] + : $item['additional_details']['description_long'], 0, 124), + $item['additional_details']['image']['source'] ?? '', + $article ? $article->getCategories()->first()->getName() : '', + $item['quantity'] ?? 0 + ); + } + + $builder->setLineItems($lineItems); + } +} diff --git a/Components/Integration/PaymentProcessors/ShopperEmailProcessor.php b/Components/Integration/PaymentProcessors/ShopperEmailProcessor.php new file mode 100644 index 00000000..4f340427 --- /dev/null +++ b/Components/Integration/PaymentProcessors/ShopperEmailProcessor.php @@ -0,0 +1,32 @@ +getStateData()->get('shopperEmail'); + + if (!empty($stateDataEmail)) { + return; + } + + $user = $context->getCheckoutSession()->get('user'); + + if (empty($user) || !isset($user['additional']['user']['email'])) { + return; + } + + $builder->setShopperEmail($user['additional']['user']['email']); + } +} diff --git a/Components/Integration/PaymentProcessors/ShopperLocaleProcessor.php b/Components/Integration/PaymentProcessors/ShopperLocaleProcessor.php new file mode 100644 index 00000000..659108f1 --- /dev/null +++ b/Components/Integration/PaymentProcessors/ShopperLocaleProcessor.php @@ -0,0 +1,20 @@ +setShopperLocale(Shopware()->Shop()->getLocale()->getLocale()); + } +} diff --git a/Components/Integration/PaymentProcessors/ShopperNameProcessor.php b/Components/Integration/PaymentProcessors/ShopperNameProcessor.php new file mode 100644 index 00000000..27cc08b5 --- /dev/null +++ b/Components/Integration/PaymentProcessors/ShopperNameProcessor.php @@ -0,0 +1,38 @@ +getStateData()->get('shopperName'); + + if (!empty($rawShopperName)) { + return; + } + + $user = $context->getCheckoutSession()->get('user'); + + if (empty($user) || !isset($user['additional']['user'])) { + return; + } + + $shopperName = new ShopperName( + $user['additional']['user']['firstname'] ?? '', + $user['additional']['user']['lastname'] ?? '' + ); + + $builder->setShopperName($shopperName); + } +} diff --git a/Components/Integration/PaymentProcessors/ShopperReferenceProcessor.php b/Components/Integration/PaymentProcessors/ShopperReferenceProcessor.php new file mode 100644 index 00000000..9dfe57f3 --- /dev/null +++ b/Components/Integration/PaymentProcessors/ShopperReferenceProcessor.php @@ -0,0 +1,31 @@ +getCheckoutSession()->get('user'); + + if (empty($user) || !isset($user['additional']['user']['id'])) { + return; + } + + $shop = Shopware()->Shop(); + + $builder->setShopperReference(ShopperReference::parse( + $shop->getHost() . '_' . $shop->getId() . '_' . $user['additional']['user']['id'] + )); + } +} diff --git a/Components/Integration/StoreService.php b/Components/Integration/StoreService.php new file mode 100644 index 00000000..3b66a9f2 --- /dev/null +++ b/Components/Integration/StoreService.php @@ -0,0 +1,212 @@ +storeRepository = $storeRepository; + $this->orderRepository = $orderRepository; + $this->connectionRepository = $connectionRepository; + } + + /** + * Returns store domain. If last character is /, delete it. + * + * @inheritDoc + */ + public function getStoreDomain(): string + { + $domain = Shopware()->Front()->Router()->assemble(['module' => 'frontend']); + + return rtrim($domain, '/'); + } + + /** + * @inheritDoc + * + * @throws Exception + */ + public function getStores(): array + { + return $this->transformStores($this->storeRepository->getShopwareSubShops()); + } + + /** + * @inheritDoc + * + * @throws Exception + */ + public function getDefaultStore(): ?Store + { + $defaultStore = $this->storeRepository->getShopwareDefaultShop(); + + return $defaultStore ? $this->transformStore($defaultStore) : null; + } + + /** + * @inheritDoc + * + * @throws Exception + */ + public function getStoreById(string $id): ?Store + { + $store = $this->storeRepository->getStoreById($id); + + return $store ? $this->transformStore($store) : null; + } + + /** + * @inheritDoc + * + * @throws InvalidShopOrderDataException + */ + public function getStoreOrderStatuses(): array + { + return $this->transformStoreOrderStatuses($this->orderRepository->getOrderStatuses()); + } + + /** + * @return array + */ + public function getDefaultOrderStatusMapping(): array + { + return [ + PaymentStates::STATE_IN_PROGRESS => Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED, + PaymentStates::STATE_PENDING => Status::PAYMENT_STATE_OPEN, + PaymentStates::STATE_PAID => Status::PAYMENT_STATE_COMPLETELY_PAID, + PaymentStates::STATE_FAILED => Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED, + PaymentStates::STATE_CANCELLED => Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED, + PaymentStates::STATE_NEW => Status::PAYMENT_STATE_OPEN + ]; + } + + /** + * Retrieves connected stores ids. + * + * @return array + */ + public function getConnectedStores(): array + { + /** @var ConnectionSettings[] $settings */ + $settings = $this->connectionRepository->select(); + $result = []; + + foreach ($settings as $item) { + $result[] = $item->getStoreId(); + } + + return $result; + } + + /** + * @param array $shopwareStatuses + * + * @return array + * + * @throws InvalidShopOrderDataException + */ + private function transformStoreOrderStatuses(array $shopwareStatuses): array + { + /** @var StateTranslatorServiceInterface $stateTranslator */ + $stateTranslator = Shopware()->Container()->get('shopware.components.state_translator'); + $storeOrderStatuses = []; + + foreach ($shopwareStatuses as $status) { + $storeOrderStatuses[] = new StoreOrderStatus( + (string)$status['id'], + $stateTranslator->translateState(StateTranslatorService::STATE_PAYMENT, $status)['description'] + ); + } + + return $storeOrderStatuses; + } + + /** + * @param ShopwareStore $store + * + * @return Store + * + * @throws Exception + */ + private function transformStore(ShopwareStore $store): Store + { + $config = clone Shopware()->Container()->get('config'); + $config->setShop($store); + + return new Store( + (string)($store->getId() ?? ''), + $store->getName() ?? '', + $config->get('setOffline') + ); + } + + /** + * @param array $shopwareStores + * + * @return Store[] + * + * @throws Exception + */ + private function transformStores(array $shopwareStores): array + { + $stores = []; + $config = clone Shopware()->Container()->get('config'); + + foreach ($shopwareStores as $shopwareStore) { + $config->setShop($shopwareStore); + $stores[] = new Store( + $shopwareStore->getId() ?? '', + $shopwareStore->getName() ?? '', + $config->get('setOffline') + ); + } + + return $stores; + } +} diff --git a/Components/Integration/SystemInfoService.php b/Components/Integration/SystemInfoService.php new file mode 100644 index 00000000..14bb9819 --- /dev/null +++ b/Components/Integration/SystemInfoService.php @@ -0,0 +1,54 @@ +configuration = $configuration; + $this->storeRepository = $repository; + } + + /** + * @inheritDoc + */ + public function getSystemInfo(): SystemInfo + { + return new SystemInfo( + $this->configuration->getIntegrationVersion(), + $this->configuration->getPluginVersion() ?? '', + json_encode($this->storeRepository->getShopTheme()) ?? '', + Shopware()->Front()->Router()->assemble(['module' => 'frontend',]) ?? '', + Shopware()->Front()->Router()->assemble(['module' => 'backend']) ?? '', + $this->configuration->getAsyncProcessUrl('test') ?? '', + 'mysql', + Shopware()->Db()->getServerVersion() ?? '' + ); + } +} diff --git a/Components/Integration/VersionService.php b/Components/Integration/VersionService.php new file mode 100644 index 00000000..5d602399 --- /dev/null +++ b/Components/Integration/VersionService.php @@ -0,0 +1,25 @@ + '', + 'replace' => '', + ]; + + /** + * @param StoreContext $storeContext + */ + public function __construct(StoreContext $storeContext) + { + $this->storeContext = $storeContext; + } + + /** + * @return string + */ + public function getWebhookUrl(): string + { + $url = Url::getFrontUrl('AdyenWebhook', 'index', ['storeId' => $this->storeContext->getStoreId()]); + + // only for development purposes + if (!empty(static::$callbackMap['host']) && !empty(static::$callbackMap['replace'])) { + $url = str_replace(static::$callbackMap['host'], static::$callbackMap['replace'], $url); + } + + return $url; + } +} diff --git a/Components/LastOpenTimeService.php b/Components/LastOpenTimeService.php new file mode 100644 index 00000000..3e2e0afa --- /dev/null +++ b/Components/LastOpenTimeService.php @@ -0,0 +1,67 @@ +repository = $repository; + } + + /** + * @param DateTime $dateTime + * + * @return void + */ + public function saveLastOpenTime(DateTime $dateTime): void + { + /** @var LastOpenTime $lastOpenTime */ + $lastOpenTime = $this->repository->selectOne(); + + if (!$lastOpenTime) { + $lastOpenTime = new LastOpenTime(); + $lastOpenTime->setTimestamp($dateTime->getTimestamp()); + $this->repository->save($lastOpenTime); + + return; + } + + $lastOpenTime->setTimestamp($dateTime->getTimestamp()); + $this->repository->update($lastOpenTime); + } + + /** + * @return DateTime + */ + public function getLastOpenTime(): DateTime + { + /** @var LastOpenTime $lastOpenTime */ + $lastOpenTime = $this->repository->selectOne(); + + return $lastOpenTime ? (new DateTime())->setTimestamp($lastOpenTime->getTimestamp()) + : (new DateTime())->setTimestamp(0); + } +} diff --git a/Components/Logger/LoggerService.php b/Components/Logger/LoggerService.php new file mode 100644 index 00000000..4151b1c6 --- /dev/null +++ b/Components/Logger/LoggerService.php @@ -0,0 +1,85 @@ + 'ERROR', + Logger::WARNING => 'WARNING', + Logger::INFO => 'INFO', + Logger::DEBUG => 'DEBUG', + ); + + /** + * @var ShopwareLogger + */ + protected $logger; + + public function __construct(ShopwareLogger $logger) + { + $this->logger = $logger; + } + + /** + * Log message in system + * + * @param LogData $data + */ + public function logMessage(LogData $data) + { + /** @var Configuration $configService */ + $configService = ServiceRegister::getService(Configuration::CLASS_NAME); + $minLogLevel = $configService->getMinLogLevel(); + $logLevel = $data->getLogLevel(); + + if (($logLevel > $minLogLevel) && !$configService->isDebugModeEnabled()) { + return; + } + + $message = 'ADYEN LOG:' . ' | ' + . 'Date: ' . date('d/m/Y') . ' | ' + . 'Time: ' . date('H:i:s') . ' | ' + . 'Log level: ' . self::$logLevelName[$logLevel] . ' | ' + . 'Message: ' . $data->getMessage(); + $context = $data->getContext(); + if (!empty($context)) { + $contextData = array(); + foreach ($context as $item) { + $contextData[$item->getName()] = print_r($item->getValue(), true); + } + + $message .= ' | ' . 'Context data: [' . json_encode($contextData) . ']'; + } + + $message .= "\n"; + + switch ($logLevel) { + case Logger::ERROR: + $this->logger->error($message); + break; + case Logger::WARNING: + $this->logger->warning($message); + break; + case Logger::INFO: + $this->logger->info($message); + break; + case Logger::DEBUG: + $this->logger->debug($message); + } + } +} diff --git a/Components/Manager/AdyenManager.php b/Components/Manager/AdyenManager.php deleted file mode 100644 index 48db69e6..00000000 --- a/Components/Manager/AdyenManager.php +++ /dev/null @@ -1,48 +0,0 @@ -modelManager = $modelManager; - } - - public function storePaymentData(PaymentInfo $transaction, string $paymentData): void - { - $transaction->setPaymentData($paymentData); - $this->modelManager->persist($transaction); - $this->modelManager->flush(); - } - - public function fetchOrderPaymentData(?Order $order): string - { - if (!$order) { - return ''; - } - - /** @var PaymentInfo $transaction */ - $transaction = $this->getPaymentInfoRepository()->findOneBy(['orderId' => $order->getId()]); - - return $transaction ? $transaction->getPaymentData() : ''; - } - - private function getPaymentInfoRepository(): EntityRepository - { - return $this->modelManager->getRepository(PaymentInfo::class); - } -} diff --git a/Components/Manager/OrderManager.php b/Components/Manager/OrderManager.php deleted file mode 100755 index 7328239a..00000000 --- a/Components/Manager/OrderManager.php +++ /dev/null @@ -1,39 +0,0 @@ -modelManager = $modelManager; - } - - public function save(Order $order): void - { - $this->modelManager->persist($order); - $this->modelManager->flush($order); - } - - public function updatePspReference(Order $order, string $pspReference): void - { - $order = $order->setTransactionId($pspReference); - $this->modelManager->persist($order); - } - - public function updatePayment(Order $order, string $pspReference, Status $paymentStatus): void - { - $order->setPaymentStatus($paymentStatus); - $order = $order->setTransactionId($pspReference); - $this->modelManager->persist($order); - } -} diff --git a/Components/Manager/OrderManagerInterface.php b/Components/Manager/OrderManagerInterface.php deleted file mode 100644 index f57aa714..00000000 --- a/Components/Manager/OrderManagerInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -modelManager = $modelManager; - } - - public function save(UserPreference $userPreference): void - { - $this->modelManager->persist($userPreference); - $this->modelManager->flush($userPreference); - } -} diff --git a/Components/Manager/UserPreferenceManagerInterface.php b/Components/Manager/UserPreferenceManagerInterface.php deleted file mode 100644 index fe8e5262..00000000 --- a/Components/Manager/UserPreferenceManagerInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -modelManager = $modelManager; - $this->notificationRepository = $modelManager->getRepository(Notification::class); - } - - /** - * @throws NoResultException - * @throws NonUniqueResultException - */ - public function getNextNotificationToHandle() - { - $builder = $this->notificationRepository->createQueryBuilder('n'); - $builder->where('n.status = :statusReceived OR n.status = :statusRetry') - ->andWhere('(n.scheduledProcessingTime <= :processingTime OR n.scheduledProcessingTime IS NULL)') - ->orderBy('n.updatedAt', 'ASC') - ->setParameter('statusReceived', NotificationStatus::STATUS_RECEIVED) - ->setParameter('statusRetry', NotificationStatus::STATUS_RETRY) - ->setParameter('processingTime', new \DateTimeImmutable()) - ->setMaxResults(1); - - return $builder->getQuery()->getSingleResult(); - } - - /** - * @throws NonUniqueResultException - * - * @return mixed|null - */ - public function getLastNotificationForOrderId(int $orderId) - { - try { - $lastNotification = $this->notificationRepository->createQueryBuilder('n') - ->where('n.orderId = :orderId') - ->setMaxResults(1) - ->orderBy('n.createdAt', 'ASC') - ->setParameter('orderId', $orderId) - ->getQuery() - ->getSingleResult(); - - return $lastNotification; - } catch (NoResultException $ex) { - return; - } - } - - /** - * @throws NonUniqueResultException - * - * @return mixed|null - */ - public function getAuthorisationNotificationForOrderId(int $orderId) - { - try { - $notification = $this->notificationRepository->createQueryBuilder('n') - ->where('n.orderId = :orderId') - ->andWhere('n.eventCode = :eventCode') - ->setMaxResults(1) - ->orderBy('n.createdAt', 'ASC') - ->setParameter('orderId', $orderId) - ->setParameter('eventCode', 'AUTHORISATION') - ->getQuery() - ->getSingleResult(); - - return $notification; - } catch (NoResultException $ex) { - return; - } - } - - public function guardDuplicate(Notification $notification): void - { - $builder = $this->notificationRepository->createQueryBuilder('n'); - $builder - ->where('n.orderId = :orderId') - ->andWhere('n.pspReference = :pspReference') - ->andWhere('n.paymentMethod = :paymentMethod') - ->andWhere('n.success = :success') - ->andWhere('n.eventCode = :eventCode') - ->andWhere('n.merchantAccountCode = :merchantAccountCode') - ->andWhere('n.amountValue = :amountValue') - ->andWhere('n.amountCurrency = :amountCurrency') - ->setParameter('orderId', $notification->getOrderId()) - ->setParameter('pspReference', $notification->getPspReference()) - ->setParameter('paymentMethod', $notification->getPaymentMethod()) - ->setParameter('success', $notification->isSuccess()) - ->setParameter('eventCode', $notification->getEventCode()) - ->setParameter('merchantAccountCode', $notification->getMerchantAccountCode()) - ->setParameter('amountValue', $notification->getAmountValue()) - ->setParameter('amountCurrency', $notification->getAmountCurrency()) - ->setMaxResults(1); - - if ($this->modelManager->contains($notification) && $notification->getId()) { - $builder - ->andWhere('n.id <> :id') - ->setParameter('id', $notification->getId()); - } - - $record = $builder->getQuery()->getOneOrNullResult(); - - if ($record instanceof Notification) { - throw DuplicateNotificationException::withNotification($record); - } - } -} diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php deleted file mode 100755 index 4fc2e8ee..00000000 --- a/Components/NotificationProcessor.php +++ /dev/null @@ -1,190 +0,0 @@ -logger = $logger; - $this->modelManager = $modelManager; - $this->eventManager = $eventManager; - $this->notificationManager = $notificationManager; - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Enlight_Event_Exception - * - * @psalm-return \Generator - */ - public function processMany(Traversable $notifications): \Generator - { - foreach ($notifications as $notification) { - try { - yield from $this->process($notification); - } catch (DuplicateNotificationException $exception) { - $notification->setStatus(NotificationStatus::STATUS_HANDLED); - $this->modelManager->persist($notification); - $this->logger->notice( - $exception->getMessage() - ); - } catch (NoNotificationProcessorFoundException $exception) { - $this->logger->notice( - 'No notification processor found', - [ - 'eventCode' => $notification->getEventCode(), - 'pspReference' => $notification->getPspReference(), - 'status' => $notification->getStatus(), - ] - ); - - yield new NotificationProcessorFeedback(false, $exception->getMessage(), $notification); - } catch (OrderNotFoundException $exception) { - $this->logger->error('No order found for notification', [ - 'eventCode' => $notification->getEventCode(), - 'status ' => $notification->getStatus(), - ]); - $this->eventManager->notify(Event::NOTIFICATION_NO_ORDER_FOUND, [ - 'notification' => $notification, - ]); - - yield new NotificationProcessorFeedback(false, $exception->getMessage(), $notification); - } finally { - $this->modelManager->flush($notification); - } - } - } - - /** - * @throws NoNotificationProcessorFoundException - * @throws OrderNotFoundException - * @throws \Doctrine\ORM\ORMException - * @throws \Enlight_Event_Exception - * - * @psalm-return \Generator - */ - private function process(Notification $notification): \Generator - { - $this->notificationManager->guardDuplicate($notification); - - $processors = $this->findProcessors($notification); - - if (empty($processors)) { - $notification->setStatus(NotificationStatus::STATUS_FATAL); - $this->modelManager->persist($notification); - - throw new NoNotificationProcessorFoundException((string) $notification->getId()); - } - - if (!$notification->getOrder()) { - $notification->setStatus(NotificationStatus::STATUS_FATAL); - $this->modelManager->persist($notification); - - throw new OrderNotFoundException((string) $notification->getOrderId()); - } - - $status = NotificationStatus::STATUS_HANDLED; - foreach ($processors as $processor) { - try { - $processor->process($notification); - } catch (NotificationException $exception) { - $status = NotificationStatus::STATUS_ERROR; - $this->logger->notice('NotificationException', [ - 'message' => $exception->getMessage(), - 'notificationId' => $exception->getNotification()->getId(), - ]); - yield new NotificationProcessorFeedback( - false, - 'NotificationException: '.$exception->getMessage(), - $notification - ); - } catch (\Exception $exception) { - $status = NotificationStatus::STATUS_FATAL; - $this->logger->notice('General Exception', [ - 'exception' => [ - 'message' => $exception->getMessage(), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - ], - 'notificationId' => $notification->getId(), - ]); - yield new NotificationProcessorFeedback( - false, - 'General Exception: '.$exception->getMessage(), - $notification - ); - } - } - - $notification->setStatus($status); - $this->modelManager->persist($notification); - - yield new NotificationProcessorFeedback(true, 'Processed '.$notification->getId(), $notification); - } - - public function addProcessor(NotificationProcessorInterface $processor): void - { - $this->processors[] = $processor; - } - - /** - * Finds all processors that support this type of Notification. - * - * @param $notification - */ - private function findProcessors(Notification $notification): array - { - $processors = []; - foreach ($this->processors as $processor) { - if ($processor->supports($notification)) { - $processors[] = $processor; - } - } - - return $processors; - } -} diff --git a/Components/NotificationProcessor/Authorisation.php b/Components/NotificationProcessor/Authorisation.php deleted file mode 100755 index b4e59fca..00000000 --- a/Components/NotificationProcessor/Authorisation.php +++ /dev/null @@ -1,105 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - $this->modelManager = $modelManager; - $this->paymentInfoRepository = $modelManager->getRepository(PaymentInfo::class); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - if (!$order) { - return; - } - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_AUTHORISATION, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - $status = $notification->isSuccess() - ? Status::PAYMENT_STATE_COMPLETELY_PAID - : Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED; - - $this->paymentStatusUpdate->updatePaymentStatus($order, $status); - - if ($notification->isSuccess()) { - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $this->paymentInfoRepository->findOneBy([ - 'orderId' => $order->getId(), - ]); - if (!$paymentInfo) { - return; - } - - $paymentInfo->setPspReference($notification->getPspReference()); - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - } -} diff --git a/Components/NotificationProcessor/Cancellation.php b/Components/NotificationProcessor/Cancellation.php deleted file mode 100755 index ac54b55a..00000000 --- a/Components/NotificationProcessor/Cancellation.php +++ /dev/null @@ -1,78 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CANCELLATION, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - if ($notification->isSuccess()) { - $this->paymentStatusUpdate->updatePaymentStatus( - $order, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED - ); - } - } -} diff --git a/Components/NotificationProcessor/Capture.php b/Components/NotificationProcessor/Capture.php deleted file mode 100755 index 50425843..00000000 --- a/Components/NotificationProcessor/Capture.php +++ /dev/null @@ -1,108 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - $this->modelManager = $modelManager; - $this->paymentInfoRepository = $modelManager->getRepository(PaymentInfo::class); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws ORMException - * @throws OptimisticLockException - * @throws TransactionRequiredException - * @throws Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - if (!$order) { - return; - } - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CAPTURE, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - if ($notification->isSuccess()) { - $this->paymentStatusUpdate->updatePaymentStatus( - $order, - Status::PAYMENT_STATE_COMPLETELY_PAID - ); - - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $this->paymentInfoRepository->findOneBy([ - 'orderId' => $order->getId(), - ]); - if (!$paymentInfo) { - return; - } - - $paymentInfo->setPspReference($notification->getPspReference()); - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - } -} diff --git a/Components/NotificationProcessor/CaptureFailed.php b/Components/NotificationProcessor/CaptureFailed.php deleted file mode 100755 index 901b69d7..00000000 --- a/Components/NotificationProcessor/CaptureFailed.php +++ /dev/null @@ -1,61 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CAPTURE_FAILED, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - } -} diff --git a/Components/NotificationProcessor/Chargeback.php b/Components/NotificationProcessor/Chargeback.php deleted file mode 100755 index bbae06fc..00000000 --- a/Components/NotificationProcessor/Chargeback.php +++ /dev/null @@ -1,81 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - if (!$order) { - $this->logger->error('No order found', [ - 'eventCode' => $notification->getEventCode(), - 'status ' => $notification->getStatus(), - ]); - - return; - } - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CHARGEBACK, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - $this->paymentStatusUpdate->updatePaymentStatus($order, Status::PAYMENT_STATE_REVIEW_NECESSARY); - } -} diff --git a/Components/NotificationProcessor/ChargebackReversed.php b/Components/NotificationProcessor/ChargebackReversed.php deleted file mode 100755 index 280531f8..00000000 --- a/Components/NotificationProcessor/ChargebackReversed.php +++ /dev/null @@ -1,81 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - if (!$order) { - $this->logger->error('No order found', [ - 'eventCode' => $notification->getEventCode(), - 'status ' => $notification->getStatus(), - ]); - - return; - } - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CHARGEBACK_REVERSED, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - $this->paymentStatusUpdate->updatePaymentStatus($order, Status::PAYMENT_STATE_COMPLETELY_PAID); - } -} diff --git a/Components/NotificationProcessor/ManualReviewAccept.php b/Components/NotificationProcessor/ManualReviewAccept.php deleted file mode 100755 index 64b2a47e..00000000 --- a/Components/NotificationProcessor/ManualReviewAccept.php +++ /dev/null @@ -1,55 +0,0 @@ -eventManager = $eventManager; - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CANCELLATION, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - } -} diff --git a/Components/NotificationProcessor/ManualReviewReject.php b/Components/NotificationProcessor/ManualReviewReject.php deleted file mode 100755 index 4f13416d..00000000 --- a/Components/NotificationProcessor/ManualReviewReject.php +++ /dev/null @@ -1,86 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - $this->configuration = $configuration; - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_CANCELLATION, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - if ($notification->isSuccess()) { - if ('Cancel' === $this->configuration->getManualReviewRejectAction()) { - $this->paymentStatusUpdate->updatePaymentStatus( - $order, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED - ); - } - } - } -} diff --git a/Components/NotificationProcessor/NotificationProcessorInterface.php b/Components/NotificationProcessor/NotificationProcessorInterface.php deleted file mode 100644 index da223857..00000000 --- a/Components/NotificationProcessor/NotificationProcessorInterface.php +++ /dev/null @@ -1,28 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_OFFER_CLOSED, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - if ($notification->isSuccess()) { - $this->paymentStatusUpdate->updateOrderStatus( - $order, - Status::ORDER_STATE_CANCELLED_REJECTED - ); - $this->paymentStatusUpdate->updatePaymentStatus( - $order, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED - ); - } - } -} diff --git a/Components/NotificationProcessor/Refund.php b/Components/NotificationProcessor/Refund.php deleted file mode 100755 index 0d116d7c..00000000 --- a/Components/NotificationProcessor/Refund.php +++ /dev/null @@ -1,75 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_REFUND, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - if ($notification->isSuccess()) { - $this->paymentStatusUpdate->updatePaymentStatus($order, Status::PAYMENT_STATE_RE_CREDITING); - } - } -} diff --git a/Components/NotificationProcessor/RefundFailed.php b/Components/NotificationProcessor/RefundFailed.php deleted file mode 100755 index 23fdf091..00000000 --- a/Components/NotificationProcessor/RefundFailed.php +++ /dev/null @@ -1,70 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_REFUND_FAILED, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - } -} diff --git a/Components/NotificationProcessor/RefundedReversed.php b/Components/NotificationProcessor/RefundedReversed.php deleted file mode 100755 index a3ea29fa..00000000 --- a/Components/NotificationProcessor/RefundedReversed.php +++ /dev/null @@ -1,73 +0,0 @@ -logger = $logger; - $this->eventManager = $eventManager; - $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); - } - - /** - * Returns boolean on whether this processor can process the Notification object. - */ - public function supports(Notification $notification): bool - { - return self::EVENT_CODE === mb_strtoupper($notification->getEventCode()); - } - - /** - * Actual processing of the notification. - * - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - * @throws \Enlight_Event_Exception - */ - public function process(Notification $notification): void - { - $order = $notification->getOrder(); - - $this->eventManager->notify( - Event::NOTIFICATION_PROCESS_REFUNDED_REVERSED, - [ - 'order' => $order, - 'notification' => $notification, - ] - ); - - $this->paymentStatusUpdate->updatePaymentStatus($order, Status::PAYMENT_STATE_COMPLETELY_PAID); - } -} diff --git a/Components/OrderMailService.php b/Components/OrderMailService.php deleted file mode 100755 index 435758d7..00000000 --- a/Components/OrderMailService.php +++ /dev/null @@ -1,90 +0,0 @@ -modelManager = $modelManager; - $this->basketService = $basketService; - } - - /** - * Executes provided callback without sending order confirmation email. - * - * @param callable $callback The callback to execute without email sending - * @param array $args The parameters to be passed to the callback, as an indexed array - */ - public function doWithoutSendingOrderConfirmationMail(callable $callback, array $args = []) - { - $this->isOrderConfirmationEmailRestricted = true; - - try { - $result = call_user_func_array($callback, $args); - } finally { - $this->isOrderConfirmationEmailRestricted = false; - } - - return $result; - } - - /** - * Sends the mail after a payment is confirmed. - */ - public function sendOrderConfirmationMail(string $orderNumber): void - { - $order = $this->basketService->getOrderByOrderNumber($orderNumber); - if (!$order) { - return; - } - - $paymentInfoRepository = $this->modelManager->getRepository(PaymentInfo::class); - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $paymentInfoRepository->findOneBy([ - 'orderId' => $order->getId(), - ]); - - if (!$paymentInfo || null === $paymentInfo->getOrdermailVariables()) { - return; - } - - $variables = json_decode($paymentInfo->getOrdermailVariables(), true); - if (is_array($variables)) { - $sOrder = Shopware()->Modules()->Order(); - - $sOrder->sUserData = $variables; - $sOrder->sBasketData = $sOrder->sBasketData ?? []; - - // Do not use CheckoutKey::CURRENCY_NAME constant because of the compatibility with SW 5.6.0 - if (!array_key_exists('sCurrencyName', $sOrder->sBasketData)) { - $sOrder->sBasketData['sCurrencyName'] = $variables['adyen_currency'] ?? null; - } - - $sOrder->sendMail($variables); - } - - $paymentInfo->setOrdermailVariables(null); - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - - public function isOrderConfirmationEmailRestricted(): bool - { - return $this->isOrderConfirmationEmailRestricted; - } -} diff --git a/Components/Payload/Chain.php b/Components/Payload/Chain.php deleted file mode 100755 index dbfd42c1..00000000 --- a/Components/Payload/Chain.php +++ /dev/null @@ -1,36 +0,0 @@ -providers = $providers; - } - - public function provide(PaymentContext $context): array - { - return array_reduce( - $this->providers, - static function(array $payload, PaymentPayloadProvider $provider) use ($context): array { - return array_merge_recursive($payload, $provider->provide($context)); - }, - [] - ); - } -} diff --git a/Components/Payload/PaymentContext.php b/Components/Payload/PaymentContext.php deleted file mode 100755 index 570b456c..00000000 --- a/Components/Payload/PaymentContext.php +++ /dev/null @@ -1,102 +0,0 @@ -paymentInfo = $paymentInfo; - $this->order = $order; - $this->basket = $basket; - $this->browserInfo = $browserInfo; - $this->shopperInfo = $shopperInfo; - $this->origin = $origin; - $this->transaction = $transaction; - $this->storePaymentMethod = $storePaymentMethod; - } - - public function getPaymentInfo(): array - { - return $this->paymentInfo; - } - - public function getOrder(): Order - { - return $this->order; - } - - public function getBasket(): sBasket - { - return $this->basket; - } - - public function getBrowserInfo(): array - { - return $this->browserInfo; - } - - public function getShopperInfo(): array - { - return $this->shopperInfo; - } - - public function getOrigin(): string - { - return $this->origin; - } - - public function getTransaction(): PaymentInfo - { - return $this->transaction; - } - - public function enableStorePaymentMethod(): bool - { - return $this->storePaymentMethod; - } -} diff --git a/Components/Payload/PaymentPayloadProvider.php b/Components/Payload/PaymentPayloadProvider.php deleted file mode 100644 index 5d6625df..00000000 --- a/Components/Payload/PaymentPayloadProvider.php +++ /dev/null @@ -1,13 +0,0 @@ -router = $router; - $this->modelManager = $modelManager; - $this->configuration = $configuration; - $this->shopwareVersionCheck = $shopwareVersionCheck; - } - - public function provide(PaymentContext $context): array - { - $returnUrl = $this->router->assemble([ - 'controller' => 'process', - 'action' => 'return', - ]).'?'.http_build_query([ - 'merchantReference' => $context->getOrder()->getNumber(), - ]); - $plugin = $this->modelManager->getRepository(Plugin::class)->findOneBy(['name' => AdyenPayment::NAME]); - - return [ - 'additionalData' => [ - 'executeThreeD' => true, - 'allow3DS2' => true, - ], - 'channel' => Channel::WEB, - 'origin' => $context->getOrigin(), - 'returnUrl' => $returnUrl, - 'merchantAccount' => $this->configuration->getMerchantAccount(), - 'applicationInfo' => [ - 'adyenPaymentSource' => [ - 'name' => $plugin->getLabel(), - 'version' => $plugin->getVersion(), - ], - 'externalPlatform' => [ - 'name' => 'Shopware', - 'version' => $this->shopwareVersionCheck->getShopwareVersion(), - 'integrator' => $plugin->getAuthor(), - ], - 'merchantApplication' => [ - 'name' => $plugin->getLabel(), - 'version' => $plugin->getVersion(), - ], - ], - ]; - } -} diff --git a/Components/Payload/Providers/BrowserInfoProvider.php b/Components/Payload/Providers/BrowserInfoProvider.php deleted file mode 100644 index f9ec7cc3..00000000 --- a/Components/Payload/Providers/BrowserInfoProvider.php +++ /dev/null @@ -1,27 +0,0 @@ - array_merge($browserInfo, $context->getBrowserInfo()), - ]; - } -} diff --git a/Components/Payload/Providers/LineItemsInfoProvider.php b/Components/Payload/Providers/LineItemsInfoProvider.php deleted file mode 100755 index a83c6b84..00000000 --- a/Components/Payload/Providers/LineItemsInfoProvider.php +++ /dev/null @@ -1,119 +0,0 @@ -priceCalculationService = $priceCalculationService; - $this->logger = $logger; - $this->adyenCurrency = new Currency(); - } - - /** - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - */ - public function provide(PaymentContext $context): array - { - return [ - 'lineItems' => array_merge( - $this->buildOrderLines($context), - $this->buildShippingLines($context) - ), - ]; - } - - private function buildOrderLines(PaymentContext $context): array - { - $orderLines = []; - $currencyCode = $context->getOrder()->getCurrency(); - - /** @var Detail $detail */ - foreach ($context->getOrder()->getDetails() as $detail) { - if (empty($detail->getArticleName())) { - $this->logger->warning( - sprintf('Skipped order detail of order #%s - empty article name.', $context->getOrder()->getId()) - ); - - continue; - } - - $orderLines[] = [ - 'quantity' => $detail->getQuantity(), - 'amountExcludingTax' => $this->adyenCurrency->sanitize( - $this->priceCalculationService->getAmountExcludingTax($detail->getPrice(), $detail->getTaxRate()), - $currencyCode - ), - 'taxPercentage' => $this->adyenCurrency->sanitize($detail->getTaxRate(), $currencyCode), - 'description' => $detail->getArticleName(), - 'id' => $detail->getId(), - 'taxAmount' => $this->adyenCurrency->sanitize( - $this->priceCalculationService->getTaxAmount($detail->getPrice(), $detail->getTaxRate()), - $currencyCode - ), - 'amountIncludingTax' => $this->adyenCurrency->sanitize($detail->getPrice(), $currencyCode), - ]; - } - - return $orderLines; - } - - private function buildShippingLines(PaymentContext $context): array - { - $currencyCode = $context->getOrder()->getCurrency(); - $amountExcludingTax = $this->adyenCurrency->sanitize( - $context->getOrder()->getInvoiceShippingNet(), - $currencyCode - ); - $amountIncludingTax = $this->adyenCurrency->sanitize( - $context->getOrder()->getInvoiceShipping(), - $currencyCode - ); - $dispatch = $context->getOrder()->getDispatch(); - - if (!$dispatch || !$dispatch->getId()) { - return []; - } - - return [ - [ - 'quantity' => 1, - 'amountExcludingTax' => $amountExcludingTax, - 'taxPercentage' => $this->adyenCurrency->sanitize( - $context->getOrder()->getInvoiceShippingTaxRate(), - $currencyCode - ), - 'description' => $dispatch->getName(), - 'id' => $dispatch->getId(), - 'taxAmount' => $amountIncludingTax - $amountExcludingTax, - 'amountIncludingTax' => $amountIncludingTax, - ], - ]; - } -} diff --git a/Components/Payload/Providers/OrderInfoProvider.php b/Components/Payload/Providers/OrderInfoProvider.php deleted file mode 100644 index 194ebb4a..00000000 --- a/Components/Payload/Providers/OrderInfoProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -getOrder()->getCurrency(); - - return [ - 'amount' => [ - 'currency' => $currencyCode, - 'value' => $adyenCurrency->sanitize($context->getOrder()->getInvoiceAmount(), $currencyCode), - ], - 'reference' => $context->getOrder()->getNumber(), - ]; - } -} diff --git a/Components/Payload/Providers/PaymentMethodProvider.php b/Components/Payload/Providers/PaymentMethodProvider.php deleted file mode 100644 index 7c506696..00000000 --- a/Components/Payload/Providers/PaymentMethodProvider.php +++ /dev/null @@ -1,21 +0,0 @@ - $context->getPaymentInfo(), - ]; - } -} diff --git a/Components/Payload/Providers/RecurringOneOffPaymentTokenProvider.php b/Components/Payload/Providers/RecurringOneOffPaymentTokenProvider.php deleted file mode 100644 index 2f4268ad..00000000 --- a/Components/Payload/Providers/RecurringOneOffPaymentTokenProvider.php +++ /dev/null @@ -1,27 +0,0 @@ -getPaymentInfo(); - $storedPaymentMethodId = (string) ($paymentInfo['storedPaymentMethodId'] ?? ''); - if ('' === $storedPaymentMethodId) { - return []; - } - - return [ - 'shopperInteraction' => ShopperInteraction::ecommerce()->shopperInteraction(), - 'recurringProcessingModel' => RecurringProcessingModel::cardOnFile()->recurringProcessingModel(), - ]; - } -} diff --git a/Components/Payload/Providers/RecurringPaymentProvider.php b/Components/Payload/Providers/RecurringPaymentProvider.php deleted file mode 100644 index bea7d3d3..00000000 --- a/Components/Payload/Providers/RecurringPaymentProvider.php +++ /dev/null @@ -1,27 +0,0 @@ -getPaymentInfo(); - $storedPaymentMethodId = (string) ($paymentInfo['storedPaymentMethodId'] ?? ''); - if ('' === $storedPaymentMethodId) { - return []; - } - - return [ - 'shopperInteraction' => ShopperInteraction::contAuth()->shopperInteraction(), - 'recurringProcessingModel' => RecurringProcessingModel::cardOnFile()->recurringProcessingModel(), - ]; - } -} diff --git a/Components/Payload/Providers/ShopperInfoProvider.php b/Components/Payload/Providers/ShopperInfoProvider.php deleted file mode 100644 index cbe0d00b..00000000 --- a/Components/Payload/Providers/ShopperInfoProvider.php +++ /dev/null @@ -1,40 +0,0 @@ - $context->getShopperInfo()['shopperIP'], - 'shopperEmail' => $context->getOrder()->getCustomer()->getEmail(), - 'shopperName' => [ - 'firstName' => $context->getOrder()->getCustomer()->getFirstname(), - 'lastName' => $context->getOrder()->getCustomer()->getLastname(), - 'gender' => $context->getOrder()->getCustomer()->getSalutation(), - ], - 'shopperLocale' => Shopware()->Shop()->getLocale()->getLocale(), - 'shopperReference' => $context->getOrder()->getCustomer()->getNumber(), - 'countryCode' => $context->getOrder()->getBilling()->getCountry()->getIso(), - 'billingAddress' => [ - 'city' => $context->getOrder()->getBilling()->getCity(), - 'country' => $context->getOrder()->getBilling()->getCountry()->getIso(), - 'stateOrProvince' => $context->getOrder()->getBilling()->getState() ? - $context->getOrder()->getBilling()->getState()->getShortCode() : - $context->getOrder()->getBilling()->getCountry()->getIso(), - 'houseNumberOrName' => 'N/A', - 'postalCode' => $context->getOrder()->getBilling()->getZipCode(), - 'street' => $context->getOrder()->getBilling()->getStreet(), - ], - ]; - } -} diff --git a/Components/Payload/Providers/StorePaymentProvider.php b/Components/Payload/Providers/StorePaymentProvider.php deleted file mode 100644 index 52f52113..00000000 --- a/Components/Payload/Providers/StorePaymentProvider.php +++ /dev/null @@ -1,18 +0,0 @@ - $context->enableStorePaymentMethod(), - ]; - } -} diff --git a/Components/PaymentMeansEnricher.php b/Components/PaymentMeansEnricher.php new file mode 100644 index 00000000..bf8ffd77 --- /dev/null +++ b/Components/PaymentMeansEnricher.php @@ -0,0 +1,296 @@ +snippets = $snippets; + $this->checkoutConfigProvider = $checkoutConfigProvider; + } + + public function enrich(array $paymentMeans): array + { + if (AdminAPI::get()->integration(Shopware()->Shop()->getId())->getState()->toArray()!==StateResponse::dashboard( + )->toArray()) { + $this->removeAdyenPaymentMeans($paymentMeans); + + return $paymentMeans; + } + + return array_merge( + $this->enrichPaymentMeans($paymentMeans), + $this->enrichStoredPaymentMeans($paymentMeans) + ); + } + + public function enrichPaymentMean(array $paymentMean, string $selectedStoredPaymentMethodId = ''): array + { + $umbrellaPaymentMean = $this->findUmbrellaPaymentMean([$paymentMean]); + if (!empty($umbrellaPaymentMean)) { + $enriched = $this->enrichStoredPaymentMeans([$paymentMean], $selectedStoredPaymentMethodId); + + return !empty($enriched) ? current($enriched):$paymentMean; + } + + $enrichedPaymentMeans = $this->enrichPaymentMeans([$paymentMean]); + + return !empty($enrichedPaymentMeans) ? current($enrichedPaymentMeans):[]; + } + + private function enrichPaymentMeans(array $paymentMeans): array + { + $paymentMethodConfigsMap = $this->getPaymentMethodConfigurationMap(); + $totalProductsAmount = Shopware()->Modules()->Basket()->sGetAmountArticles(); + $currencyFactor = Shopware()->Shop()->getCurrency()->getFactor(); + + return array_map( + static function (array $paymentMean) use ( + $totalProductsAmount, + $currencyFactor, + $paymentMethodConfigsMap + ) { + $adyenPaymentType = Plugin::getAdyenPaymentType($paymentMean['name']); + $paymentMean['isAdyenPaymentMethod'] = Plugin::isAdyenPaymentMean($paymentMean['name']); + $paymentMean['isStoredPaymentMethod'] = false; + $paymentMean['adyenPaymentType'] = $adyenPaymentType; + if ( + $paymentMean['isAdyenPaymentMethod'] && + array_key_exists($paymentMean['adyenPaymentType'], $paymentMethodConfigsMap) + ) { + $paymentMethod = $paymentMethodConfigsMap[$paymentMean['adyenPaymentType']]; + $paymentMean['image'] = $paymentMethod->getLogo(); + $paymentMean['description'] = $paymentMethod->getName(); + $paymentMean['additionaldescription'] = $paymentMethod->getDescription(); + $paymentMean['surchargeAmount'] = self::calculateSurchargeAmount( + $paymentMethod, + $currencyFactor, + (float)$totalProductsAmount['totalAmount'] + ); + $paymentMean['surchargeLimit'] = self::calculateSurchargeLimit($paymentMethod); + } + + return $paymentMean; + }, + $this->getOnlyAvailablePaymentMeans($paymentMeans) + ); + } + + private static function calculateSurchargeAmount( + PaymentMethod $paymentMethod, + float $currencyFactor, + float $productAmount + ): float { + $surchargeType = $paymentMethod->getSurchargeType(); + $fixedAmount = (float)$paymentMethod->getFixedSurcharge() * $currencyFactor; + $limit = (float)$paymentMethod->getSurchargeLimit() * $currencyFactor; + $percent = $paymentMethod->getPercentSurcharge(); + + if ($surchargeType === 'fixed') { + return $fixedAmount; + } + + if ($surchargeType === 'percent') { + $amount = ($productAmount) / 100 * $percent; + + return $limit && $amount > $limit ? $limit : $amount; + } + + if ($surchargeType === 'combined') { + $amount = ($productAmount + $fixedAmount) / 100 * $percent; + + return $limit ? (min($amount + $fixedAmount, $limit)) : $amount + $fixedAmount ; + } + + return 0; + } + + private static function calculateSurchargeLimit(PaymentMethod $paymentMethod): float + { + $surchargeType = $paymentMethod->getSurchargeType(); + + if ($surchargeType === 'fixed') { + return 0; + } + + if ($surchargeType === 'percent') { + return $paymentMethod->getSurchargeLimit() ?? 0; + } + + if ($surchargeType === 'combined') { + return $paymentMethod->getFixedSurcharge() && $paymentMethod->getSurchargeLimit( + ) ? $paymentMethod->getSurchargeLimit() - $paymentMethod->getFixedSurcharge( + ) : $paymentMethod->getSurchargeLimit() ?? 0; + } + + return 0; + } + + private function enrichStoredPaymentMeans(array $paymentMeans, string $selectedStoredPaymentMethodId = ''): array + { + $umbrellaPaymentMean = $this->findUmbrellaPaymentMean($paymentMeans); + if (empty($umbrellaPaymentMean)) { + return []; + } + + $paymentMethodConfigsMap = $this->getPaymentMethodConfigurationMap(); + $checkoutConfig = $this->checkoutConfigProvider->getCheckoutConfig(); + + if (!$checkoutConfig->isSuccessful()) { + return []; + } + + $storedPaymentMethodsResponse = $checkoutConfig->getStoredPaymentMethodResponse(); + if (!empty($selectedStoredPaymentMethodId)) { + $storedPaymentMethodsResponse = $this->filterSelectedStoredPaymentMethod( + $storedPaymentMethodsResponse, + $selectedStoredPaymentMethodId + ); + } + + return array_map( + function ( + PaymentMethodResponse $paymentMethodResponse + ) use ( + $umbrellaPaymentMean, + $paymentMethodConfigsMap + ) { + $paymentMean = [ + 'isAdyenPaymentMethod' => true, + 'isStoredPaymentMethod' => true, + 'storedPaymentMethodId' => $paymentMethodResponse->getMetaData()['id'], + 'adyenPaymentType' => $paymentMethodResponse->getType(), + 'description' => $paymentMethodResponse->getName(), + 'additionaldescription' => sprintf( + $this->snippets + ->getNamespace('frontend/adyen/checkout') + ->get( + 'payment/adyen/card_number_ending_on', + 'Card number ending on: %s', + true + ), + $paymentMethodResponse->getMetaData()['lastFour'] + ), + ]; + + if (array_key_exists($paymentMean['adyenPaymentType'], $paymentMethodConfigsMap)) { + $paymentMethod = $paymentMethodConfigsMap[$paymentMean['adyenPaymentType']]; + $paymentMean['image'] = $paymentMethod->getLogo(); + $paymentMean['additionaldescription'] = implode( + '. ', [$paymentMethod->getDescription(), $paymentMean['additionaldescription']] + ); + } + + return array_merge($umbrellaPaymentMean, $paymentMean); + }, + $storedPaymentMethodsResponse + ); + } + + private function getOnlyAvailablePaymentMeans(array $paymentMeans): array + { + $checkoutConfig = $this->checkoutConfigProvider->getCheckoutConfig(); + + $availablePaymentMethodTypes = []; + if ($checkoutConfig->isSuccessful()) { + $availablePaymentMethodTypes = array_map(static function (PaymentMethodResponse $paymentMethodResponse) { + return $paymentMethodResponse->getType(); + }, $checkoutConfig->getPaymentMethodResponse()); + } + + return array_filter( + array_map(static function (array $paymentMean) use ($availablePaymentMethodTypes) { + if (!Plugin::isAdyenPaymentMean($paymentMean['name'])) { + return $paymentMean; + } + + $paymentMeanType = Plugin::getAdyenPaymentType($paymentMean['name']); + + return in_array($paymentMeanType, $availablePaymentMethodTypes, true) ? $paymentMean:null; + }, $paymentMeans) + ); + } + + /** + * @return array + * @throws InvalidCurrencyCode + */ + private function getPaymentMethodConfigurationMap(): array + { + $checkoutConfig = $this->checkoutConfigProvider->getCheckoutConfig(); + if (!$checkoutConfig->isSuccessful()) { + return []; + } + + $paymentMethodConfigsMap = []; + foreach ($checkoutConfig->getPaymentMethodsConfiguration() as $paymentMethodConfig) { + $paymentMethodConfigsMap[$paymentMethodConfig->getCode()] = $paymentMethodConfig; + } + + return $paymentMethodConfigsMap; + } + + private function findUmbrellaPaymentMean(array $paymentMeans): array + { + foreach ($paymentMeans as $paymentMean) { + if ($paymentMean['name']===AdyenPayment::STORED_PAYMENT_UMBRELLA_NAME) { + return $paymentMean; + } + } + + return []; + } + + /** + * @param PaymentMethodResponse[] $storedPaymentMethodsResponse + * @param string $selectedStoredPaymentMethodId + * @return PaymentMethodResponse[] + */ + private function filterSelectedStoredPaymentMethod( + array $storedPaymentMethodsResponse, + string $selectedStoredPaymentMethodId + ): array { + foreach ($storedPaymentMethodsResponse as $paymentMethodResponse) { + if ($paymentMethodResponse->getMetaData()['id']===$selectedStoredPaymentMethodId) { + return [$paymentMethodResponse]; + } + } + + return []; + } + + private function removeAdyenPaymentMeans(array &$paymentMeans) + { + foreach ($paymentMeans as $key => $paymentMean) { + if (strpos($paymentMean['name'], 'adyen')!==false) { + unset($paymentMeans[$key]); + } + } + } +} diff --git a/Components/PaymentStatusUpdate.php b/Components/PaymentStatusUpdate.php deleted file mode 100755 index 6871cd28..00000000 --- a/Components/PaymentStatusUpdate.php +++ /dev/null @@ -1,105 +0,0 @@ -modelManager = $modelManager; - $this->eventManager = $eventManager; - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - */ - public function updateOrderStatus(Order $order, int $statusId): void - { - $orderStatus = $this->modelManager->find(Status::class, $statusId); - - if ($this->logger) { - $this->logger->debug('Update order status', [ - 'number' => $order->getNumber(), - 'oldStatus' => $order->getOrderStatus()->getName(), - 'newStatus' => $orderStatus->getName(), - ]); - } - - $this->eventManager->notify( - Event::ORDER_STATUS_CHANGED, - [ - 'order' => $order, - 'newStatus' => $orderStatus, - ] - ); - - $order->setOrderStatus($orderStatus); - $this->modelManager->persist($order); - $this->modelManager->flush(); - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - */ - public function updatePaymentStatus(Order $order, int $statusId): void - { - $paymentStatus = $this->modelManager->find(Status::class, $statusId); - - if ($this->logger) { - $this->logger->debug('Update order payment status', [ - 'number' => $order->getNumber(), - 'oldStatus' => $order->getPaymentStatus()->getName(), - 'newStatus' => $paymentStatus->getName(), - ]); - } - - $this->eventManager->notify( - Event::ORDER_PAYMENT_STATUS_CHANGED, - [ - 'order' => $order, - 'newStatus' => $paymentStatus, - ] - ); - - $order->setPaymentStatus($paymentStatus); - $this->modelManager->persist($order); - $this->modelManager->flush(); - } - - /** - * @return static - */ - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } -} diff --git a/Components/ShopwareVersionCheck.php b/Components/ShopwareVersionCheck.php old mode 100755 new mode 100644 index 4ec2c6db..d7588aa7 --- a/Components/ShopwareVersionCheck.php +++ b/Components/ShopwareVersionCheck.php @@ -5,8 +5,6 @@ namespace AdyenPayment\Components; use OutOfBoundsException; -use PackageVersions\Versions; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; class ShopwareVersionCheck @@ -16,15 +14,9 @@ class ShopwareVersionCheck /** @var ContainerInterface */ private $container; - /** @var LoggerInterface */ - private $logger; - - public function __construct( - ContainerInterface $container, - LoggerInterface $logger - ) { + public function __construct(ContainerInterface $container) + { $this->container = $container; - $this->logger = $logger; } public function isHigherThanShopwareVersion(string $shopwareVersion): bool @@ -42,23 +34,16 @@ public function isHigherThanShopwareVersion(string $shopwareVersion): bool return version_compare($shopwareVersion, $version, '<'); } - /** - * @psalm-suppress UndefinedClass - */ public function getShopwareVersion(): string { $version = $this->container->get('shopware.release')->getVersion(); - if (self::SHOPWARE === $version) { + if (self::SHOPWARE === $version && class_exists('\PackageVersions\Versions')) { try { - [$composerVersion, $sha] = explode('@', Versions::getVersion('shopware/shopware')); + [$composerVersion, $sha] = explode('@', \PackageVersions\Versions::getVersion('shopware/shopware')); $version = $composerVersion; } catch (OutOfBoundsException $ex) { - $this->logger->error('OutOfBoundsException', [ - 'message' => $ex->getMessage(), - 'file' => $ex->getFile(), - 'line' => $ex->getLine(), - ]); + /* Intentionally left empty */ } } diff --git a/Components/TextNotificationManager.php b/Components/TextNotificationManager.php deleted file mode 100644 index 6e968cd6..00000000 --- a/Components/TextNotificationManager.php +++ /dev/null @@ -1,39 +0,0 @@ -modelManager = $modelManager; - $this->textNotificationRepository = $modelManager->getRepository(TextNotification::class); - } - - public function getTextNextNotificationsToHandle(): array - { - $builder = $this->textNotificationRepository->createQueryBuilder('n'); - $builder->orderBy('n.createdAt', 'ASC')->setMaxResults(20); - - return $builder->getQuery()->getResult(); - } -} diff --git a/Components/TransactionDetailsService.php b/Components/TransactionDetailsService.php new file mode 100644 index 00000000..1cee13c9 --- /dev/null +++ b/Components/TransactionDetailsService.php @@ -0,0 +1,38 @@ + $item) { + $result[$key]['amountCurrency'] = $item['amountCurrency'] ? Currencies::getSymbol($item['amountCurrency']) : ''; + } + + return $result; + } +} diff --git a/Components/UninstallService.php b/Components/UninstallService.php new file mode 100644 index 00000000..0f5cc0dc --- /dev/null +++ b/Components/UninstallService.php @@ -0,0 +1,92 @@ +storeService = $storeService; + } + + /** + * @throws Exception + */ + public function uninstall(): void + { + $connectedStores = $this->storeService->getConnectedStores(); + + foreach ($connectedStores as $store) { + StoreContext::doWithStore( + $store, + function () { + $this->doUninstall(); + } + ); + } + } + + /** + * @return void + * + * @throws Exception + */ + private function doUninstall(): void + { + try { + $this->deleteImages(); + + /** @var DisconnectService $disconnectService */ + $disconnectService = ServiceRegister::getService(DisconnectService::class); + $disconnectService->removeWebhook(); + $disconnectService->disconnectIntegration(); + } catch (Exception $exception) { + Shopware()->Container()->get('corelogger')->warning( + 'Uninstallation for store ' + . StoreContext::getInstance()->getStoreId() . ' failed: ' . $exception->getMessage() + ); + } + } + + /** + * @throws FailedToRetrievePaymentMethodsException + * @throws Exception + */ + private function deleteImages(): void + { + $storeId = StoreContext::getInstance()->getStoreId(); + /** @var FileService $disconnectService */ + $fileService = ServiceRegister::getService(FileService::class); + $fileService->delete('adyen-giving-logo-store-' . $storeId); + $fileService->delete('adyen-giving-background-store-' . $storeId); + + /** @var PaymentMethodConfigRepository $paymentService */ + $paymentService = ServiceRegister::getService(PaymentMethodConfigRepository::class); + $paymentMethods = $paymentService->getConfiguredPaymentMethods(); + foreach ($paymentMethods as $method) { + $fileService->delete($method->getMethodId() . '_store_' . $storeId); + } + } +} diff --git a/Components/WebComponents/ApplePayConfigProvider.php b/Components/WebComponents/ApplePayConfigProvider.php deleted file mode 100644 index 1c2cc251..00000000 --- a/Components/WebComponents/ApplePayConfigProvider.php +++ /dev/null @@ -1,50 +0,0 @@ -getUserData()['additional']['payment']; - - if (!isset($paymentData['metadata']['configuration'])) { - $configuration = []; - } - - $configuration['merchantName'] = $paymentData['metadata']['configuration']['merchantName'] ?? ''; - $configuration['merchantId'] = $paymentData['metadata']['configuration']['merchantId'] ?? ''; - - return [ - 'countryCode' => (string) ($context->getUserData()['additional']['country']['countryiso'] ?? ''), - 'amount' => [ - 'value' => (new Currency())->sanitize( - (float) ($context->getBasket()['AmountNumeric'] ?? 0.0), - (string) ($context->getBasket()['sCurrencyName'] ?? '') - ), - 'currency' => (string) ($context->getBasket()['sCurrencyName'] ?? ''), - ], - 'configuration' => $configuration, - ]; - } -} diff --git a/Components/WebComponents/ConfigContext.php b/Components/WebComponents/ConfigContext.php deleted file mode 100755 index 454d21c3..00000000 --- a/Components/WebComponents/ConfigContext.php +++ /dev/null @@ -1,41 +0,0 @@ -getSubject(); - $userData = $subject->View()->getAssign('sUserData') ?? []; - $basket = $subject->View()->getAssign('sBasket') ?? []; - - $new = new self(); - $new->userData = $userData; - $new->basket = $basket; - - return $new; - } - - public function getUserData(): array - { - return $this->userData; - } - - public function getBasket(): array - { - return $this->basket; - } -} diff --git a/Components/WebComponents/ConfigProvider.php b/Components/WebComponents/ConfigProvider.php deleted file mode 100644 index 9279525d..00000000 --- a/Components/WebComponents/ConfigProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -configuration = $configuration; - } - - /** - * @return array{ - * environment: "PRODUCTION"|"TEST", - * countryCode: string, - * amount: array{ - * value: int, - * currency: string, - * }, - * configuration: array{ - * gatewayMerchantId: string - * }, - * } - */ - public function __invoke(ConfigContext $context): array - { - return [ - 'environment' => $this->configuration->isTestMode() ? 'TEST' : 'PRODUCTION', - 'countryCode' => (string) ($context->getUserData()['additional']['country']['countryiso'] ?? ''), - 'amount' => [ - 'value' => (new Currency())->sanitize( - (float) ($context->getBasket()['AmountNumeric'] ?? 0.0), - (string) ($context->getBasket()['sCurrencyName'] ?? '') - ), - 'currency' => (string) ($context->getBasket()['sCurrencyName'] ?? ''), - ], - 'configuration' => [ - 'gatewayMerchantId' => $this->configuration->getMerchantAccount(), - ], - ]; - } -} diff --git a/Controllers/Backend/AdyenAuthorization.php b/Controllers/Backend/AdyenAuthorization.php new file mode 100644 index 00000000..d452d9e9 --- /dev/null +++ b/Controllers/Backend/AdyenAuthorization.php @@ -0,0 +1,53 @@ +Request()->get('storeId'); + $connectionRequest = new ConnectionRequest( + $storeId, + $requestData['mode'] ?? '', + $requestData['testData']['apiKey'] ?? '', + $requestData['testData']['merchantId'] ?? '', + $requestData['liveData']['apiKey'] ?? '', + $requestData['liveData']['merchantId'] ?? '' + ); + + $result = AdminAPI::get()->connection($storeId)->connect($connectionRequest); + + $this->returnAPIResponse($result); + } + + /** + * @return void + */ + public function getConnectionSettingsAction(): void + { + $storeId = $this->Request()->get('storeId'); + $result = AdminAPI::get()->connection($storeId)->getConnectionSettings(); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenAutoTest.php b/Controllers/Backend/AdyenAutoTest.php new file mode 100644 index 00000000..f0284897 --- /dev/null +++ b/Controllers/Backend/AdyenAutoTest.php @@ -0,0 +1,71 @@ +autoTest()->startAutoTest(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function autoTestStatusAction(): void + { + $queueItemId = $this->Request()->get('queueItemId'); + $result = AdminAPI::get()->autoTest()->autoTestStatus($queueItemId ?? 0); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws RepositoryNotRegisteredException + * @throws Exception + */ + public function getReportAction(): void + { + $result = AdminAPI::get()->autoTest()->autoTestReport(); + + $data = json_encode($result->toArray(), JSON_PRETTY_PRINT); + $response = $this->Response(); + $response->headers->set('content-description', 'File Transfer'); + $response->headers->set('content-type', 'application/octet-stream'); + $response->headers->set('content-disposition', 'attachment; filename=auto-test-logs.json'); + $response->headers->set('cache-control', 'public', true); + $response->headers->set('content-length', (string)strlen($data)); + $response->sendHeaders(); + + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $out = fopen('php://output', 'wb'); + + fwrite($out, $data); + fclose($out); + } +} diff --git a/Controllers/Backend/AdyenDebug.php b/Controllers/Backend/AdyenDebug.php new file mode 100644 index 00000000..76dfbb44 --- /dev/null +++ b/Controllers/Backend/AdyenDebug.php @@ -0,0 +1,34 @@ +debug()->getDebugMode(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + */ + public function setDebugModeAction(): void + { + $requestData = Request::getPostData(); + $result = AdminAPI::get()->debug()->setDebugMode($requestData['debugMode'] ?? false); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenDisconnect.php b/Controllers/Backend/AdyenDisconnect.php new file mode 100644 index 00000000..5efae0a3 --- /dev/null +++ b/Controllers/Backend/AdyenDisconnect.php @@ -0,0 +1,76 @@ +ajaxResponseSetterPreDispatch(); + $this->fileService = $this->get(FileService::class); + } + + /** + * @throws Exception + */ + public function disconnectAction(): void + { + $storeId = $this->Request()->get('storeId'); + + $response = AdminAPI::get()->integration($storeId)->getState(); + if ($response->toArray() === StateResponse::onboarding()->toArray()) { + $this->returnAPIResponse($response); + + return; + } + StoreContext::doWithStore($storeId, function () { + $this->removeImages(); + }); + $result = AdminAPI::get()->disconnect($storeId)->disconnect(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws Exception + */ + private function removeImages(): void + { + $storeId = StoreContext::getInstance()->getStoreId(); + $this->fileService->delete('adyen-giving-logo-store-' . $storeId); + $this->fileService->delete('adyen-giving-background-store-' . $storeId); + + foreach ($this->getPaymentMethodConfigRepository()->getConfiguredPaymentMethods() as $method) { + $this->fileService->delete($method->getMethodId() . '_store_' . $storeId); + } + } + + /** + * @return PaymentMethodConfigRepository + */ + private function getPaymentMethodConfigRepository(): PaymentMethodConfigRepository + { + return ServiceRegister::getService(PaymentMethodConfigRepository::class); + } +} diff --git a/Controllers/Backend/AdyenGeneralSettings.php b/Controllers/Backend/AdyenGeneralSettings.php new file mode 100644 index 00000000..8a846037 --- /dev/null +++ b/Controllers/Backend/AdyenGeneralSettings.php @@ -0,0 +1,52 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->generalSettings($storeId)->getGeneralSettings(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws InvalidCaptureDelayException + * @throws InvalidRetentionPeriodException + * @throws InvalidCaptureTypeException + */ + public function putGeneralSettingsAction(): void + { + $requestData = Request::getPostData(); + $storeId = $this->Request()->get('storeId'); + $generalSettingsRequest = new GeneralSettingsRequest( + $requestData['basketItemSync'] ?? false, + $requestData['capture'] ?? '', + $requestData['captureDelay'] ?? 1, + $requestData['shipmentStatus'] ?? '', + $requestData['retentionPeriod'] ?? '' + ); + + $result = AdminAPI::get()->generalSettings($storeId)->saveGeneralSettings($generalSettingsRequest); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenGivingSettings.php b/Controllers/Backend/AdyenGivingSettings.php new file mode 100644 index 00000000..acfe6c0f --- /dev/null +++ b/Controllers/Backend/AdyenGivingSettings.php @@ -0,0 +1,110 @@ +ajaxResponseSetterPreDispatch(); + $this->fileService = $this->get(FileService::class); + } + + /** + * @return void + */ + public function getAdyenGivingSettingsAction(): void + { + $storeId = $this->Request()->get('storeId'); + $result = AdminAPI::get()->adyenGivingSettings($storeId)->getAdyenGivingSettings(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + */ + public function putAdyenGivingSettingsAction(): void + { + $requestData = $this->Request()->getParams(); + $storeId = $requestData['storeId']; + + $result = AdminAPI::get()->adyenGivingSettings($storeId)->saveAdyenGivingSettings($this->createGivingRequest($storeId)); + + $this->returnAPIResponse($result); + } + + private function createGivingRequest(string $storeId): AdyenGivingSettingsRequest + { + $requestData = $this->Request()->getParams(); + + if ($requestData['enableAdyenGiving'] === 'false') { + $this->fileService->delete('adyen-giving-logo-store-' . $storeId); + $this->fileService->delete('adyen-giving-background-store-' . $storeId); + + return new AdyenGivingSettingsRequest(false); + } + + $this->saveImages($storeId); + + return new AdyenGivingSettingsRequest( + $requestData['enableAdyenGiving'] === 'true', + $requestData['charityName'] ?? '', + $requestData['charityDescription'] ?? '', + $requestData['charityMerchantAccount'] ?? '', + $requestData['donationAmount'] ?? '', + $requestData['charityWebsite'] ?? '', + $this->fileService->getLogoUrl('adyen-giving-logo-store-' . $storeId) ?? '', + $this->fileService->getLogoUrl('adyen-giving-background-store-' . $storeId) ?? '' + ); + } + + private function saveImages(string $storeId): void + { + $logo = $this->Request()->files->get('logo'); + $logoContents = null; + + if ($logo) { + $filePath = (string)$logo->getRealPath(); + $stream = fopen($filePath, 'rb'); + $logoContents = stream_get_contents($stream); + } + + if ($logoContents) { + $this->fileService->write($logoContents, 'adyen-giving-logo-store-' . $storeId); + } + + $backgroundImage = $this->Request()->files->get('backgroundImage'); + $imageContents = null; + + if ($backgroundImage) { + $filePath = (string)$backgroundImage->getRealPath(); + $stream = fopen($filePath, 'rb'); + $imageContents = stream_get_contents($stream); + } + + if ($imageContents) { + $this->fileService->write($imageContents, 'adyen-giving-background-store-' . $storeId); + } + } +} diff --git a/Controllers/Backend/AdyenMerchant.php b/Controllers/Backend/AdyenMerchant.php new file mode 100644 index 00000000..c3a212b2 --- /dev/null +++ b/Controllers/Backend/AdyenMerchant.php @@ -0,0 +1,25 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->merchant($storeId)->getMerchants(); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenMerchantActions.php b/Controllers/Backend/AdyenMerchantActions.php new file mode 100644 index 00000000..69182261 --- /dev/null +++ b/Controllers/Backend/AdyenMerchantActions.php @@ -0,0 +1,75 @@ +Request()->get('currency'); + $amount = $this->Request()->get('amount'); + $merchantReference = $this->Request()->get('merchantReference'); + $storeId = $this->Request()->get('storeId'); + + $response = AdminAPI::get()->capture($storeId)->handle($merchantReference, $amount, $currency); + + if (!$response->isSuccessful()) { + $namespace = Shopware()->Snippets()->getNamespace('backend/adyen/configuration'); + $translatedString = $namespace->get( + 'payment/adyen/capturerequestfail', + 'Capture request failed. Please check Adyen configuration. Reason: ' + ); + $this->Response()->setHttpResponseCode($response->getStatusCode()); + $this->Response()->setBody($translatedString . $response->toArray()['errorMessage'] ?? ''); + } + } + + /** + * @throws InvalidMerchantReferenceException + */ + public function cancelAction(): void + { + $storeId = $this->Request()->get('storeId'); + $merchantReference = $this->Request()->get('merchantReference'); + + $response = AdminAPI::get()->cancel($storeId)->handle($merchantReference); + + if (!$response->isSuccessful()) { + $namespace = Shopware()->Snippets()->getNamespace('backend/adyen/configuration'); + $translatedString = $namespace->get( + 'payment/adyen/cancelrequestfail', + 'Cancel request failed. Please check Adyen configuration. Reason: ' + ); + $this->Response()->setHttpResponseCode($response->getStatusCode()); + $this->Response()->setBody($translatedString . $response->toArray()['errorMessage'] ?? ''); + } + } + + public function refundAction(): void + { + $storeId = $this->Request()->get('storeId'); + $currency = $this->Request()->get('currency'); + $amount = $this->Request()->get('amount'); + $merchantReference = $this->Request()->get('merchantReference'); + + $response = AdminAPI::get()->refund($storeId)->handle($merchantReference, $amount, $currency); + + if (!$response->isSuccessful()) { + $namespace = Shopware()->Snippets()->getNamespace('backend/adyen/configuration'); + $translatedString = $namespace->get( + 'payment/adyen/refundrequestfail', + 'Refund request failed. Please check Adyen configuration. Reason: ' + ); + $this->Response()->setHttpResponseCode($response->getStatusCode()); + $this->Response()->setBody($translatedString . $response->toArray()['errorMessage'] ?? ''); + } + } +} diff --git a/Controllers/Backend/AdyenNotifications.php b/Controllers/Backend/AdyenNotifications.php new file mode 100644 index 00000000..390a2b3d --- /dev/null +++ b/Controllers/Backend/AdyenNotifications.php @@ -0,0 +1,94 @@ +ajaxResponseSetterPreDispatch(); + $this->orderRepository = $this->get(OrderRepository::class); + } + + /** + * @return void + * + * @throws Exception + */ + public function getNotificationsAction(): void + { + $storeId = $this->Request()->get('storeId'); + $page = $this->Request()->get('page', 1); + $limit = $this->Request()->get('limit', 10); + $result = AdminAPI::get()->shopNotifications($storeId)->getNotifications($page, $limit); + + if (!$result->isSuccessful()) { + $this->returnAPIResponse($result); + + return; + } + + $jsonResponse = $result->toArray(); + $map = $this->mapOrderNumbers($this->getMerchantReferences($jsonResponse['notifications'])); + + foreach ($jsonResponse['notifications'] as $key => $item) { + $jsonResponse['notifications'][$key]['orderId'] = $map[$item['orderId']]; + } + + $this->Response()->setHeader('Content-Type', 'application/json'); + $this->Response()->setBody(json_encode($jsonResponse)); + } + + /** + * @param array $notifications + * + * @return array + */ + private function getMerchantReferences(array $notifications): array + { + return array_unique( + array_map(static function (array $notifications) { + return $notifications['orderId']; + }, $notifications) + ); + } + + /** + * @param string[] $references + * + * @return array + */ + private function mapOrderNumbers(array $references): array + { + if (empty($references)) { + return []; + } + + $ordersMap = $this->orderRepository->getOrderNumbersFor($references); + + $orderNumbers = []; + foreach ($references as $reference) { + $orderNumbers[$reference] = array_key_exists($reference, $ordersMap) ? $ordersMap[$reference] : ''; + } + + return $orderNumbers; + } +} diff --git a/Controllers/Backend/AdyenOrderStatusMap.php b/Controllers/Backend/AdyenOrderStatusMap.php new file mode 100644 index 00000000..133e8a7b --- /dev/null +++ b/Controllers/Backend/AdyenOrderStatusMap.php @@ -0,0 +1,39 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->orderMappings($storeId)->getOrderStatusMap(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + */ + public function putOrderStatusMapAction(): void + { + $requestData = Request::getPostData(); + $storeId = $this->Request()->get('storeId'); + $orderStatusMapRequest = OrderMappingsRequest::parse($requestData); + + $result = AdminAPI::get()->orderMappings($storeId)->saveOrderStatusMap($orderStatusMapRequest); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenOrderStatuses.php b/Controllers/Backend/AdyenOrderStatuses.php new file mode 100644 index 00000000..5e6278da --- /dev/null +++ b/Controllers/Backend/AdyenOrderStatuses.php @@ -0,0 +1,25 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->store($storeId)->getStoreOrderStatuses(); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenPayment.php b/Controllers/Backend/AdyenPayment.php new file mode 100644 index 00000000..aaa685f2 --- /dev/null +++ b/Controllers/Backend/AdyenPayment.php @@ -0,0 +1,169 @@ +ajaxResponseSetterPreDispatch(); + $this->fileService = $this->get(FileService::class); + } + + /** + * @return void + * + * @throws Exception + */ + public function getAvailableMethodsAction(): void + { + $storeId = $this->Request()->get('storeId'); + + $result = AdminAPI::get()->payment($storeId)->getAvailablePaymentMethods(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws Exception + */ + public function getConfiguredMethodsAction(): void + { + $storeId = $this->Request()->get('storeId'); + + $result = AdminAPI::get()->payment($storeId)->getConfiguredPaymentMethods(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws Exception + */ + public function getMethodByIdAction(): void + { + $storeId = $this->Request()->get('storeId'); + $id = $this->Request()->get('methodId'); + + $result = AdminAPI::get()->payment($storeId)->getMethodById($id); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws PaymentMethodDataEmptyException + * @throws Exception + */ + public function saveMethodAction(): void + { + $storeId = $this->Request()->get('storeId'); + $method = $this->createPaymentMethodRequest(); + + $result = AdminAPI::get()->payment($storeId)->saveMethodConfiguration($method); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws PaymentMethodDataEmptyException + * @throws Exception + */ + public function updateMethodAction(): void + { + $storeId = $this->Request()->get('storeId'); + $method = $this->createPaymentMethodRequest(); + + $result = AdminAPI::get()->payment($storeId)->updateMethodConfiguration($method); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws Exception + */ + public function deleteMethodAction(): void + { + $storeId = $this->Request()->get('storeId'); + $methodId = $this->Request()->get('methodId'); + + $result = AdminAPI::get()->payment($storeId)->deletePaymentMethodById($methodId); + $this->fileService->delete($methodId . '_store_' . $storeId); + + $this->returnAPIResponse($result); + } + + /** + * @return PaymentMethodRequest + * + * @throws PaymentMethodDataEmptyException + * @throws Exception + */ + private function createPaymentMethodRequest(): PaymentMethodRequest + { + $requestData = $this->Request()->getParams(); + $logo = $this->Request()->files->get('logo'); + $logoContents = null; + + if ($logo) { + $filePath = (string)$logo->getRealPath(); + $stream = fopen($filePath, 'rb'); + $logoContents = stream_get_contents($stream); + } + + if ($logoContents) { + $this->fileService->write($logoContents, $requestData['methodId'] . '_store_' . $requestData['storeId']); + } + + if (!isset($requestData['currencies']) || $requestData['currencies'] === '') { + $requestData['currencies'] = []; + } + + if ($requestData['currencies'] === 'ANY') { + $requestData['currencies'] = ['ANY']; + } + + if (is_string($requestData['currencies'])) { + $requestData['currencies'] = [$requestData['currencies']]; + } + + if (!isset($requestData['countries']) || $requestData['countries'] === '') { + $requestData['countries'] = []; + } + + if ($requestData['countries'] === 'ANY') { + $requestData['countries'] = ['ANY']; + } + + $requestData['additionalData'] = !empty($requestData['additionalData']) ? + json_decode($requestData['additionalData'], true) : []; + $requestData['logo'] = $this->fileService->getLogoUrl($requestData['methodId'] . '_store_' . $requestData['storeId']); + + return PaymentMethodRequest::parse($requestData); + } +} diff --git a/Controllers/Backend/AdyenPaymentMain.php b/Controllers/Backend/AdyenPaymentMain.php new file mode 100644 index 00000000..c72b6854 --- /dev/null +++ b/Controllers/Backend/AdyenPaymentMain.php @@ -0,0 +1,143 @@ +View()->assign([ + 'response' => [ + 'urls' => $this->getUrls(), + 'lang' => $this->getTranslations(), + 'sidebar' => $this->getSidebarContent(), + ], + 'assetsVersion' => Plugin::getVersion(), + ]); + } + + private function getUrls(): array + { + return [ + 'connection' => [ + 'getSettingsUrl' => Url::getBackendUrl('AdyenAuthorization', 'getConnectionSettings') . '/storeId/{storeId}', + 'submitUrl' => Url::getBackendUrl('AdyenAuthorization', 'connect') . '/storeId/{storeId}', + 'disconnectUrl' => Url::getBackendUrl('AdyenDisconnect', 'disconnect') . '/storeId/{storeId}', + 'getMerchantsUrl' => Url::getBackendUrl('AdyenMerchant', 'index') . '/storeId/{storeId}', + 'validateConnectionUrl' => Url::getBackendUrl('AdyenValidateConnection', 'validate') . '/storeId/{storeId}', + 'validateWebhookUrl' => Url::getBackendUrl('AdyenWebhookValidation', 'validate') . '/storeId/{storeId}', + ], + 'payments' => [ + 'getConfiguredPaymentsUrl' => Url::getBackendUrl('AdyenPayment', 'getConfiguredMethods') . '/storeId/{storeId}', + 'addMethodConfigurationUrl' => Url::getBackendUrl('AdyenPayment', 'saveMethod') . '/storeId/{storeId}', + 'getMethodConfigurationUrl' => Url::getBackendUrl('AdyenPayment', 'getMethodById') . '/storeId/{storeId}/methodId/{methodId}', + 'saveMethodConfigurationUrl' => Url::getBackendUrl('AdyenPayment', 'updateMethod') . '/storeId/{storeId}', + 'getAvailablePaymentsUrl' => Url::getBackendUrl('AdyenPayment', 'getAvailableMethods') . '/storeId/{storeId}', + 'deleteMethodConfigurationUrl' => Url::getBackendUrl('AdyenPayment', 'deleteMethod') . '/storeId/{storeId}/methodId/{methodId}', + ], + 'stores' => [ + 'storesUrl' => Url::getBackendUrl('AdyenShopInformation', 'getStores'), + 'currentStoreUrl' => Url::getBackendUrl('AdyenShopInformation', 'getCurrentStore'), + ], + 'integration' => [ + 'stateUrl' => Url::getBackendUrl('AdyenState', 'index') . '/storeId/{storeId}', + ], + 'version' => [ + 'versionUrl' => Url::getBackendUrl('AdyenVersion', 'getVersion'), + ], + 'settings' => [ + 'getShippingStatusesUrl' => Url::getBackendUrl('AdyenOrderStatuses', 'getOrderStatuses') . '/storeId/{storeId}', + 'getSettingsUrl' => Url::getBackendUrl('AdyenGeneralSettings', 'getGeneralSettings') . '/storeId/{storeId}', + 'saveSettingsUrl' => Url::getBackendUrl('AdyenGeneralSettings', 'putGeneralSettings') . '/storeId/{storeId}', + 'getOrderMappingsUrl' => Url::getBackendUrl('AdyenOrderStatusMap', 'getOrderStatusMap') . '/storeId/{storeId}', + 'saveOrderMappingsUrl' => Url::getBackendUrl('AdyenOrderStatusMap', 'putOrderStatusMap') . '/storeId/{storeId}', + 'getGivingUrl' => Url::getBackendUrl('AdyenGivingSettings', 'getAdyenGivingSettings') . '/storeId/{storeId}', + 'saveGivingUrl' => Url::getBackendUrl('AdyenGivingSettings', 'putAdyenGivingSettings') . '/storeId/{storeId}', + 'webhookValidationUrl' => Url::getBackendUrl('AdyenWebhookValidation', 'validate') . '/storeId/{storeId}', + 'downloadWebhookReportUrl' => Url::getBackendUrl('AdyenWebhookValidation', 'validateReport') . '/storeId/{storeId}', + 'integrationValidationUrl' => Url::getBackendUrl('AdyenAutoTest', 'startAutoTest'), + 'integrationValidationTaskCheckUrl' => Url::getBackendUrl('AdyenAutoTest', 'autoTestStatus') . '/queueItemId/{queueItemId}', + 'downloadIntegrationReportUrl' => Url::getBackendUrl('AdyenAutoTest', 'getReport'), + 'downloadSystemInfoFileUrl' => Url::getBackendUrl('AdyenSystemInfo', 'systemInfo'), + 'getSystemInfoUrl' => Url::getBackendUrl('AdyenDebug', 'getDebugMode'), + 'saveSystemInfoUrl' => Url::getBackendUrl('AdyenDebug', 'setDebugMode') + ], + 'notifications' => [ + 'getShopEventsNotifications' => Url::getBackendUrl('AdyenNotifications', 'getNotifications') . '/storeId/{storeId}', + 'getWebhookEventsNotifications' => Url::getBackendUrl('AdyenWebhookNotifications', 'getWebhookNotifications') . '/storeId/{storeId}' + ] + ]; + } + + /** + * @return array + */ + private function getTranslations(): array + { + return [ + 'default' => $this->getDefaultTranslations(), + 'current' => $this->getCurrentTranslations(), + ]; + } + + /** + * @return false|string + */ + private function getSidebarContent() + { + return file_get_contents(__DIR__ . '/../../Resources/views/backend/_resources/templates/sidebar.html'); + } + + /** + * @return mixed + */ + private function getDefaultTranslations() + { + $baseDir = __DIR__ . '/../../Resources/views/backend/_resources/lang/'; + + return json_decode(file_get_contents($baseDir . 'en.json'), true); + } + + /** + * @return mixed + */ + private function getCurrentTranslations() + { + $baseDir = __DIR__ . '/../../Resources/views/backend/_resources/lang/'; + $locale = $this->getLocale(); + + return json_decode(file_get_contents($baseDir . $locale . '.json'), true); + } + + /** + * @return string + */ + private function getLocale(): string + { + $locale = 'en'; + + if ($auth = Shopware()->Container()->get('auth')) { + $locale = substr($auth->getIdentity()->locale->getLocale, 0, 2); + } + + return in_array($locale, ['en', 'de']) ? $locale : 'en'; + } +} diff --git a/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php b/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php deleted file mode 100644 index 0f8ef813..00000000 --- a/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php +++ /dev/null @@ -1,68 +0,0 @@ -leftJoin('notification.order', 'nOrder') - ->addSelect(['nOrder']); - - return $builder; - } - - /** - * Joins order to notification in detail query - * - * @param int $id - * @return \Shopware\Components\Model\QueryBuilder - */ - protected function getDetailQuery($id) - { - $builder = parent::getDetailQuery($id); - - $builder->leftJoin('notification.order', 'nOrder') - ->addSelect(['nOrder']); - - return $builder; - } - - /** - * Returns distinct Event Codes in json array - */ - public function getEventCodesAction(): void - { - $eventCodes = $this->getManager()->createQueryBuilder() - ->select('n.eventCode') - ->distinct() - ->from(Notification::class, 'n') - ->getQuery() - ->getResult(); - - $this->view->assign('eventCodes', $eventCodes); - } - - /** - * Returns all NotificationStatusses in json array - */ - public function getNotificationStatussesAction(): void - { - $statusses = array_map(function ($status) { - return ['status' => $status]; - }, NotificationStatus::getStatusses()); - $this->view->assign('statusses', $statusses); - } -} diff --git a/Controllers/Backend/AdyenPaymentRefund.php b/Controllers/Backend/AdyenPaymentRefund.php deleted file mode 100644 index f2a1ec30..00000000 --- a/Controllers/Backend/AdyenPaymentRefund.php +++ /dev/null @@ -1,30 +0,0 @@ -Request()->getParam('orderId'); - $notificationManager = $this->get(RefundService::class); - - $refund = $notificationManager->doRefund($orderId); - - $this->View()->assign('refundReference', $refund->getPspReference()); - } - - /** - * Returns a list with actions which should not be validated for CSRF protection - * - * @return string[] - * - * @psalm-return array{0: 'refund'} - */ - public function getWhitelistedCSRFActions(): array - { - return ['refund']; - } -} diff --git a/Controllers/Backend/AdyenShopInformation.php b/Controllers/Backend/AdyenShopInformation.php new file mode 100644 index 00000000..148bd4fc --- /dev/null +++ b/Controllers/Backend/AdyenShopInformation.php @@ -0,0 +1,54 @@ +store('')->getStores(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + */ + public function getCurrentStoreAction(): void + { + $sessionStoreId = Shopware()->BackendSession()->get('adyenStoreId'); + + if ($sessionStoreId) { + Shopware()->BackendSession()->remove('adyenStoreId'); + $this->returnAPIResponse( + new StoreResponse( + $this->getStoreService()->getStoreById($sessionStoreId) + ) + ); + } + + $result = AdminAPI::get()->store('')->getCurrentStore(); + + $this->returnAPIResponse($result); + } + + /** + * @return StoreService + */ + private function getStoreService(): StoreService + { + return ServiceRegister::getService(StoreService::class); + } +} diff --git a/Controllers/Backend/AdyenShopNotifications.php b/Controllers/Backend/AdyenShopNotifications.php new file mode 100644 index 00000000..1fa90b38 --- /dev/null +++ b/Controllers/Backend/AdyenShopNotifications.php @@ -0,0 +1,110 @@ +getConnectedStores(); + $result = []; + $lastOpenTime = $this->getOpenTimeService()->getLastOpenTime(); + + foreach ($storeIds as $storeId) { + $hasNotifications = $this->storeHasNotifications($storeId, $lastOpenTime); + + if (!$hasNotifications) { + continue; + } + + $store = $this->getStoreService()->getStoreById($storeId); + + if (!$store) { + continue; + } + + $result[] = [ + 'storeId' => $storeId, + 'storeName' => $store->getStoreName(), + ]; + } + + $this->getOpenTimeService()->saveLastOpenTime(new DateTime()); + + $this->Response()->setBody(json_encode($result)); + } + + /** + * @param string $storeId + * @param DateTime $lastOpenTime + * + * @return bool + * + * @throws Exception + */ + private function storeHasNotifications(string $storeId, DateTime $lastOpenTime): bool + { + return StoreContext::doWithStore( + $storeId, + [$this->getNotificationService(), 'hasSignificantNotifications'], + [$lastOpenTime] + ); + } + + /** + * @return array + */ + private function getConnectedStores(): array + { + return $this->getStoreService()->getConnectedStores(); + } + + /** + * @return ShopNotificationService + */ + private function getNotificationService(): ShopNotificationService + { + if ($this->notificationService === null) { + $this->notificationService = ServiceRegister::getService(ShopNotificationService::class); + } + + return $this->notificationService; + } + + /** + * @return StoreService + */ + private function getStoreService(): StoreService + { + return ServiceRegister::getService(StoreService::class); + } + + /** + * @return LastOpenTimeService + */ + private function getOpenTimeService(): LastOpenTimeService + { + if ($this->openTimeService === null) { + $this->openTimeService = ServiceRegister::getService(LastOpenTimeService::class); + } + + return $this->openTimeService; + } +} diff --git a/Controllers/Backend/AdyenState.php b/Controllers/Backend/AdyenState.php new file mode 100644 index 00000000..e75403ae --- /dev/null +++ b/Controllers/Backend/AdyenState.php @@ -0,0 +1,23 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->integration($storeId)->getState(); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenSystemInfo.php b/Controllers/Backend/AdyenSystemInfo.php new file mode 100644 index 00000000..115e733f --- /dev/null +++ b/Controllers/Backend/AdyenSystemInfo.php @@ -0,0 +1,159 @@ +ajaxResponseSetterPreDispatch(); + $this->repository = $this->get(PaymentMeanRepository::class); + } + + /** + * @return void + * + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + * @throws Exception + */ + public function systemInfoAction(): void + { + $file = $this->createZip(); + + $response = $this->Response(); + $response->headers->set('content-description', 'File Transfer'); + $response->headers->set('content-type', 'application/octet-stream'); + $response->headers->set('content-disposition', 'attachment; filename=' . self::SYSTEM_INFO_FILE_NAME); + $response->headers->set('cache-control', 'public', true); + $response->headers->set('content-length', (string)filesize($file)); + $response->sendHeaders(); + + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + + $out = fopen('php://output', 'wb'); + $file = fopen($file, 'rb'); + + stream_copy_to_stream($file, $out); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + private function createZip() + { + $file = tempnam(sys_get_temp_dir(), 'adyen_system_info'); + + $zip = new ZipArchive(); + $zip->open($file, ZipArchive::CREATE); + + $info = AdminAPI::get()->systemInfo()->getSystemInfo()->toArray(); + $autoTestReport = AdminAPI::get()->autoTest()->autoTestReport()->toArray(); + + $zip->addFromString(self::PHP_INFO_FILE_NAME, $info['phpInfo']); + $zip->addFromString( + self::QUEUE_INFO_FILE_NAME, + json_encode($info['queueItems'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + $zip->addFromString( + self::CONFIGURED_PAYMENT_METHODS, + json_encode($info['paymentMethods'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + $zip->addFromString( + self::SYSTEM_INFO, + json_encode($info['systemInfo'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + $zip->addFromString( + self::CONNECTION_SETTINGS, + json_encode($info['connectionSettings'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + $zip->addFromString(self::WEBHOOK_VALIDATION, $info['webhookValidation']); + $zip->addFromString( + self::AUTO_TEST, + json_encode($autoTestReport, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + $zip->addFromString(self::ADYEN_SPECIFIC_LOGS, self::getLogs(self::ADYEN_SPECIFIC_LOG_FILE_NAME)); + $zip->addFromString(self::SHOPWARE_LOG_FILE, self::getLogs(self::CORE)); + $zip->addFromString( + self::SHOPWARE_PAYMENT_METHODS, + json_encode($this->repository->getAdyenPaymentMeans(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + + $zip->close(); + + return $file; + } + + /** + * Retrieves contents of log files. + * + * @param $logFileName + * + * @return string + */ + protected static function getLogs($logFileName): string + { + $dir = Shopware()->DocPath('var/log'); + $files = glob($dir . $logFileName . '*.log'); + $filesWithTimeStamp = []; + $cutoff = time() - self::CUTOFF; + + foreach ($files as $fileName) { + $time = filectime($fileName); + if ($time >= $cutoff) { + $filesWithTimeStamp[] = [ + 'path' => $fileName, + 'timestamp' => $time, + ]; + } + } + + $result = ''; + + if (!empty($filesWithTimeStamp)) { + array_multisort(array_column($filesWithTimeStamp, 'timestamp'), SORT_ASC, $filesWithTimeStamp); + foreach ($filesWithTimeStamp as $item) { + if ($contents = file_get_contents($item['path'])) { + $result .= $contents . "\n"; + } + } + } + + return $result; + } +} diff --git a/Controllers/Backend/AdyenTransaction.php b/Controllers/Backend/AdyenTransaction.php new file mode 100644 index 00000000..554e4759 --- /dev/null +++ b/Controllers/Backend/AdyenTransaction.php @@ -0,0 +1,62 @@ +ajaxResponseSetterPreDispatch(); + } + + /** + * @return void + * + * @throws CurrencyMismatchException + * @throws Exception + */ + public function getAction(): void + { + $merchantReference = $this->Request()->get('temporaryId'); + $storeId = $this->Request()->get('storeId'); + $result = StoreContext::doWithStore( + $storeId, + [$this->getTransactionDetailsService($storeId), 'getTransactionDetails'], + [$merchantReference, $storeId] + ); + + $this->Response()->setBody(json_encode($result)); + } + + /** + * @param string $storeId + * + * @return TransactionDetailsService + * + * @throws Exception + */ + private function getTransactionDetailsService(string $storeId): TransactionDetailsService + { + return StoreContext::doWithStore( + $storeId, + [ServiceRegister::getInstance(), 'getService'], + [TransactionDetailsService::class] + ); + } +} diff --git a/Controllers/Backend/AdyenValidateConnection.php b/Controllers/Backend/AdyenValidateConnection.php new file mode 100644 index 00000000..f98678bd --- /dev/null +++ b/Controllers/Backend/AdyenValidateConnection.php @@ -0,0 +1,37 @@ +Request()->get('storeId'); + $connectionRequest = new ConnectionRequest( + $storeId, + $requestData['mode'] ?? '', + $requestData['testData']['apiKey'] ?? '', + $requestData['testData']['merchantId'] ?? '', + $requestData['liveData']['apiKey'] ?? '', + $requestData['liveData']['merchantId'] ?? '' + ); + $result = AdminAPI::get()->testConnection($storeId)->test($connectionRequest); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenVersion.php b/Controllers/Backend/AdyenVersion.php new file mode 100644 index 00000000..d3d9c8be --- /dev/null +++ b/Controllers/Backend/AdyenVersion.php @@ -0,0 +1,16 @@ +versions()->getVersionInfo(); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Backend/AdyenWebhookNotifications.php b/Controllers/Backend/AdyenWebhookNotifications.php new file mode 100644 index 00000000..38353543 --- /dev/null +++ b/Controllers/Backend/AdyenWebhookNotifications.php @@ -0,0 +1,95 @@ +ajaxResponseSetterPreDispatch(); + $this->orderRepository = $this->get(OrderRepository::class); + } + + /** + * @return void + * + * @throws Exception + */ + public function getWebhookNotificationsAction(): void + { + $storeId = $this->Request()->get('storeId'); + $page = $this->Request()->get('page', 1); + $limit = $this->Request()->get('limit', 10); + $result = AdminAPI::get()->webhookNotifications($storeId)->getNotifications($page, $limit); + $jsonResponse = $result->toArray(); + + if (!$result->isSuccessful()) { + $this->returnAPIResponse($result); + + return; + } + + $map = $this->mapOrderNumbers($this->getMerchantReferences($jsonResponse['notifications'])); + + foreach ($jsonResponse['notifications'] as $key => $item) { + $jsonResponse['notifications'][$key]['orderId'] = $map[$item['orderId']]; + } + + $this->Response()->setHeader('Content-Type', 'application/json'); + $this->Response()->setBody(json_encode($jsonResponse)); + } + + /** + * @param array $logs + * + * @return array + */ + private function getMerchantReferences(array $logs): array + { + return array_unique( + array_map(static function (array $log) { + return $log['orderId']; + }, $logs) + ); + } + + /** + * @param string[] $references + * + * @return array + */ + private function mapOrderNumbers(array $references): array + { + if (empty($references)) { + return []; + } + + $ordersMap = $this->orderRepository->getOrderNumbersFor($references); + + $orderNumbers = []; + foreach ($references as $reference) { + $orderNumbers[$reference] = array_key_exists($reference, $ordersMap) ? $ordersMap[$reference] : ''; + } + + return $orderNumbers; + } +} diff --git a/Controllers/Backend/AdyenWebhookValidation.php b/Controllers/Backend/AdyenWebhookValidation.php new file mode 100644 index 00000000..b5b7fbb2 --- /dev/null +++ b/Controllers/Backend/AdyenWebhookValidation.php @@ -0,0 +1,51 @@ +Request()->get('storeId'); + $result = AdminAPI::get()->webhookValidation($storeId)->validate(); + + $this->returnAPIResponse($result); + } + + /** + * @return void + * + * @throws Exception + */ + public function validateReportAction(): void + { + $storeId = $this->Request()->get('storeId'); + $result = AdminAPI::get()->webhookValidation($storeId)->report(); + + $data = json_encode($result->toArray(), JSON_PRETTY_PRINT); + $response = $this->Response(); + $response->headers->set('content-description', 'File Transfer'); + $response->headers->set('content-type', 'application/octet-stream'); + $response->headers->set('content-disposition', 'attachment; filename=webhook-validation.json'); + $response->headers->set('cache-control', 'public', true); + $response->headers->set('content-length', (string)strlen($data)); + $response->sendHeaders(); + + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $out = fopen('php://output', 'wb'); + + fwrite($out, $data); + fclose($out); + } +} diff --git a/Controllers/Backend/ImportPaymentMethods.php b/Controllers/Backend/ImportPaymentMethods.php deleted file mode 100755 index 4636b2e0..00000000 --- a/Controllers/Backend/ImportPaymentMethods.php +++ /dev/null @@ -1,64 +0,0 @@ -cacheManager = $this->get('shopware.cache_manager'); - $this->paymentMethodImporter = $this->get(PaymentMethodImporter::class); - $this->logger = $this->get('adyen_payment.logger'); - } - - public function importAction(): void - { - try { - $this->cacheManager->clearConfigCache(); - - $total = $success = 0; - foreach ($this->paymentMethodImporter->importAll() as $result) { - ++$total; - if ($result->isSuccess()) { - ++$success; - } - } - - $this->response->setHttpResponseCode(Response::HTTP_OK); - $this->View()->assign('responseText', sprintf('Imported %s of %s payment method(s).%s', - $success, - $total, - $total !== $success ? ' Details can be found in adyen log.' : '' - )); - } catch (\Exception $e) { - $this->logger->error($e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - $this->View()->assign('responseText', - sprintf('Import of payment methods failed. Please check the logs for more details.') - ); - } - } -} diff --git a/Controllers/Backend/InstallApplePayMerchantAssociation.php b/Controllers/Backend/InstallApplePayMerchantAssociation.php deleted file mode 100755 index c1a27f5a..00000000 --- a/Controllers/Backend/InstallApplePayMerchantAssociation.php +++ /dev/null @@ -1,60 +0,0 @@ -associationFileInstaller = $this->container->get(MerchantAssociationFileInstaller::class); - $this->logger = $this->get('adyen_payment.logger'); - } - - public function installAction(): void - { - try { - $installResults = iterator_to_array(($this->associationFileInstaller)()); - /** @var InstallResult $finalResult */ - $finalResult = array_pop($installResults); - - if ($finalResult->success()) { - $this->response->setHttpResponseCode(Response::HTTP_OK); - $this->View()->assign('responseText', sprintf( - 'Installed Adyen ApplePay Merchant Association file successfully.%s', - $finalResult->fallback() ? ' (used fallback)' : '' - )); - - return; - } - - $this->response->setHttpResponseCode(Response::HTTP_SERVICE_UNAVAILABLE); - $this->View()->assign( - 'responseText', - "Could not install Adyen's ApplePay Merchant Association file. This issue might be related to file permissions on the server. Please check logs for more details." - ); - - } catch (\Exception $exception) { - $this->logger->error($exception->getMessage(), ['exception' => $exception]); - - $this->response->setHttpResponseCode(Response::HTTP_SERVICE_UNAVAILABLE); - $this->View()->assign( - 'responseText', - "Could not download Adyen's ApplePay Merchant Association file (see logs for details)." - ); - } - } -} diff --git a/Controllers/Backend/RegisterApplePayAssociationUrl.php b/Controllers/Backend/RegisterApplePayAssociationUrl.php deleted file mode 100755 index e94805f2..00000000 --- a/Controllers/Backend/RegisterApplePayAssociationUrl.php +++ /dev/null @@ -1,35 +0,0 @@ -seoIndexer = $this->container->get('seoindex'); - $this->modules = $this->container->get('modules'); - $this->seoUrlWriter = $this->container->get(SeoUrlWriter::class); - } - - public function registerAction(): void - { - $this->seoIndexer->registerShop($this->Request()->getParam('shopId')); - ($this->seoUrlWriter)(); - - $this->View()->assign(['success' => true]); - } -} diff --git a/Controllers/Backend/TestAdyenApi.php b/Controllers/Backend/TestAdyenApi.php deleted file mode 100755 index cfcc8dbf..00000000 --- a/Controllers/Backend/TestAdyenApi.php +++ /dev/null @@ -1,56 +0,0 @@ -cacheManager = $this->get('shopware.cache_manager'); - $this->apiConfigValidator = $this->get(ConfigValidator::class); - $this->usedFallbackConfigRule = $this->get(UsedFallbackConfigRule::class); - } - - public function runAction(): void - { - $shopId = (int) $this->request->get('shopId', 1); - $this->cacheManager->clearConfigCache(); - - $violations = $this->apiConfigValidator->validate($shopId); - if ($violations->count() > 0) { - $this->response->setHttpResponseCode(Response::HTTP_BAD_REQUEST); - $this->View()->assign('responseText', implode("\n", array_map( - static function (ConstraintViolationInterface $violation) { - return $violation->getMessage(); - }, - iterator_to_array($violations) - ))); - - return; - } - - $usedFallback = ($this->usedFallbackConfigRule)($shopId); - $this->response->setHttpResponseCode(Response::HTTP_OK); - $this->View()->assign('responseText', sprintf( - '%sAdyen API connection successful.', - $usedFallback ? "Fallback to main shop API configuration
" : '' - )); - } -} diff --git a/Controllers/Common/AjaxResponseSetter.php b/Controllers/Common/AjaxResponseSetter.php new file mode 100644 index 00000000..40b6cd83 --- /dev/null +++ b/Controllers/Common/AjaxResponseSetter.php @@ -0,0 +1,37 @@ +Front()->Plugins()->ViewRenderer()->setNoRender(); + } + + /** + * @param Response $response + * + * @return void + */ + public function returnAPIResponse(Response $response): void + { + $this->Response()->setHeader('Content-Type', 'application/json'); + $this->Response()->setBody( + json_encode($response->toArray()) + ); + + if (!$response->isSuccessful()) { + $this->Response()->setHttpResponseCode($response->getStatusCode()); + } + } +} diff --git a/Controllers/Frontend/Adyen.php b/Controllers/Frontend/Adyen.php deleted file mode 100755 index 72aacc60..00000000 --- a/Controllers/Frontend/Adyen.php +++ /dev/null @@ -1,354 +0,0 @@ -adyenManager = $this->get(AdyenManager::class); - $this->adyenCheckout = $this->get(PaymentMethodService::class); - $this->logger = $this->get('adyen_payment.logger'); - $this->paymentPayloadProvider = $this->get(PaymentPayloadProvider::class); - $this->basketService = $this->get(BasketService::class); - $this->orderMailService = $this->get(OrderMailService::class); - $this->orderManager = $this->get(OrderManagerInterface::class); - } - - public function ajaxDoPaymentAction(): void - { - $this->Request()->setHeader('Content-Type', 'application/json'); - $this->Front()->Plugins()->ViewRenderer()->setNoRender(); - - if (!Shopware()->Modules()->Admin()->sCheckUser()) { - $this->Response()->setHttpResponseCode(401); - return; - } - - $context = $this->createPaymentContext(); - $paymentInfo = []; - - try { - $payload = $this->paymentPayloadProvider->provide($context); - $checkout = $this->adyenCheckout->getCheckout(); - $paymentInfo = $checkout->payments($payload); - - $this->adyenManager->storePaymentData( - $context->getTransaction(), - $paymentInfo['paymentData'] ?? '' - ); - $this->updateOrderTransactionId($paymentInfo); - - $this->handlePaymentData($paymentInfo); - - $this->Response()->setBody(json_encode( - [ - 'status' => 'success', - 'content' => $paymentInfo, - 'adyenTransactionId' => $context->getTransaction()->getId(), - 'sUniqueID' => $context->getOrder()->getTemporaryId(), - ] - )); - } catch (AdyenException $ex) { - $this->logger->debug('AdyenException during doPayment', [ - 'message' => $ex->getMessage(), - 'file' => $ex->getFile(), - 'line' => $ex->getLine(), - ]); - - $this->Response()->setBody(json_encode( - [ - 'status' => 'error', - 'content' => $ex->getMessage(), - ] - )); - - $this->updateOrderTransactionId($paymentInfo); - $this->basketService->cancelAndRestoreByOrderNumber($context->getOrder()->getNumber()); - } - } - - /** - * @throws AdyenException - * - * @deprecated will be removed in 3.0.0 to move closer to a generic implementation, - * use paymentDetailsAction() instead - */ - public function ajaxThreeDsAction(): void - { - $threeDSResult = (string) ($this->Request()->getPost()['details']['threeDSResult'] ?? ''); - if ('' === $threeDSResult) { - $this->logger->error('3DS missing data', [ - 'action' => $this->Request()->getPost()['action'] ?? '', - 'threeDSResult' => substr($threeDSResult, -5), - 'paymentData' => substr( $this->Request()->getPost()['paymentData'] ?? '', -5), - ]); - } - - $this->paymentDetailsAction(); - } - - /** - * @return void - * @throws AdyenException - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function paymentDetailsAction(): void - { - $this->Request()->setHeader('Content-Type', 'application/json'); - $this->Front()->Plugins()->ViewRenderer()->setNoRender(); - - if (!Shopware()->Modules()->Admin()->sCheckUser()) { - $this->Response()->setHttpResponseCode(401); - $this->tryOrderCancelByTransactionId($this->Request()->getPost('adyenTransactionId')); - - return; - } - - $payload = array_intersect_key($this->Request()->getPost(), ['details' => true]); - $checkout = $this->adyenCheckout->getCheckout(); - $paymentInfo = $checkout->paymentsDetails($payload); - - $this->updateOrderTransactionId($paymentInfo); - $this->handlePaymentData($paymentInfo); - - $this->Response()->setBody(json_encode($paymentInfo)); - } - - /** - * @return PaymentContext - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function createPaymentContext(): PaymentContext - { - $paymentInfo = json_decode($this->Request()->getPost('paymentMethod') ?? '{}', true); - $transaction = $this->prepareTransaction(); - $order = $this->prepareOrder($transaction); - $browserInfo = $this->Request()->getPost('browserInfo'); - $shopperInfo = $this->getShopperInfo(); - $origin = $this->Request()->getPost('origin'); - $storePaymentMethod = (bool) json_decode($this->Request()->getPost('storePaymentMethod', false), true); - - return new PaymentContext( - $paymentInfo, - $order, - Shopware()->Modules()->Basket(), - $browserInfo, - $shopperInfo, - $origin, - $transaction, - $storePaymentMethod - ); - } - - /** - * @return PaymentInfo - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function prepareTransaction(): PaymentInfo - { - $transaction = new PaymentInfo(); - $transaction->setOrderId(-1); - $transaction->setPspReference(''); - - $this->getModelManager()->persist($transaction); - $this->getModelManager()->flush($transaction); - - return $transaction; - } - - /** - * @param PaymentInfo $transaction - * - * @return Order - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function prepareOrder(PaymentInfo $transaction): Order - { - $signature = "adyen_{$transaction->getId()}_{$this->persistBasket()}"; - - Shopware()->Session()->offsetSet( - AdyenPayment::SESSION_ADYEN_PAYMENT_INFO_ID, - $transaction->getId() - ); - - if ($this->Request()->getParam('sComment') !== null) { - Shopware()->Session()->offsetSet('sComment', $this->Request()->getParam('sComment')); - } - - $orderNumber = $this->orderMailService->doWithoutSendingOrderConfirmationMail( - [$this, 'saveOrder'], [$transaction->getId(), $signature, Status::PAYMENT_STATE_OPEN, false] - ); - - /** @var Order $order */ - $order = $this->getModelManager()->getRepository(Order::class)->findOneBy([ - 'number' => $orderNumber, - ]); - - $transaction->setOrder($order); - - $this->getModelManager()->persist($transaction); - $this->getModelManager()->flush($transaction); - - return $order; - } - - /** - * @return array - * - * @psalm-return array{shopperIP: mixed} - */ - private function getShopperInfo(): array - { - return [ - 'shopperIP' => $this->request->getClientIp(), - ]; - } - - /** - * @param $paymentInfo - * - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function handlePaymentData($paymentInfo): void - { - $rawResultCode = (string) ($paymentInfo['resultCode'] ?? ''); - if (!PaymentResultCode::exists($rawResultCode)) { - $this->handlePaymentDataError($paymentInfo); - return; - } - - $resultCode = PaymentResultCode::load((string) ($paymentInfo['resultCode'] ?? '')); - if ( - !$resultCode->equals(PaymentResultCode::authorised()) && - !$resultCode->equals(PaymentResultCode::identifyShopper()) && - !$resultCode->equals(PaymentResultCode::challengeShopper()) && - !$resultCode->equals(PaymentResultCode::pending()) && - !$resultCode->equals(PaymentResultCode::redirectShopper()) - ) { - $this->handlePaymentDataError($paymentInfo); - } - } - - /** - * @param $paymentInfo - * - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function handlePaymentDataError(array $paymentResponseInfo): void - { - if (array_key_exists('merchantReference', $paymentResponseInfo)) { - $this->basketService->cancelAndRestoreByOrderNumber($paymentResponseInfo['merchantReference']); - return; - } - - if (isset($paymentResponseInfo['action']['merchantReference'])) { - $this->basketService->cancelAndRestoreByOrderNumber($paymentResponseInfo['action']['merchantReference']); - } - } - - /** - * @return void - * @throws Enlight_Event_Exception - * @throws Enlight_Exception - * @throws Zend_Db_Adapter_Exception - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - private function tryOrderCancelByTransactionId($adyenTransactionId): void - { - /** @var PaymentInfo $transaction */ - $transaction = $this->getModelManager()->getRepository(PaymentInfo::class)->findOneBy([ - 'id' => $adyenTransactionId, - ]); - if (!$transaction) { - return; - } - - $this->basketService->cancelAndRestoreByOrderNumber($transaction->getOrdernumber()); - } - - /** - * @param array $paymentResponseInfo - * @return void - */ - private function updateOrderTransactionId(array $paymentResponseInfo): void - { - $pspReference = $paymentResponseInfo['pspReference'] ?? ''; - if (empty($pspReference)) { - return; - } - - $merchantReference = $paymentResponseInfo['merchantReference'] ?? null; - if (!$merchantReference && isset($paymentResponseInfo['action']['merchantReference'])) { - $merchantReference = $paymentResponseInfo['action']['merchantReference']; - } - - if (!$merchantReference) { - return; - } - - $order = $this->basketService->getOrderByOrderNumber($merchantReference); - if ($order) { - $this->orderManager->updatePspReference($order, $pspReference); - $this->orderManager->save($order); - } - } -} diff --git a/Controllers/Frontend/AdyenAsyncProcess.php b/Controllers/Frontend/AdyenAsyncProcess.php new file mode 100644 index 00000000..cdae4d2b --- /dev/null +++ b/Controllers/Frontend/AdyenAsyncProcess.php @@ -0,0 +1,55 @@ +Request()->getParam('guid', ''); + $autoTest = $this->Request()->getParam('auto-test', false); + + if ($autoTest) { + $autoTestService = new AutoTestService(); + $autoTestService->setAutoTestMode(); + Logger::logInfo('Received auto-test async process request', 'Integration'); + } else { + Logger::logDebug("Received async process request with guid [{$guid}].", 'Integration'); + } + + if ($guid !== 'auto-configure') { + /** @var AsyncProcessService $asyncProcessService */ + $asyncProcessService = ServiceRegister::getService(AsyncProcessService::CLASS_NAME); + $asyncProcessService->runProcess($guid); + } + + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $this->Response()->setBody( + json_encode(['response', ['success' => true]]) + ); + + } +} diff --git a/Controllers/Frontend/AdyenConfig.php b/Controllers/Frontend/AdyenConfig.php deleted file mode 100644 index 277820b2..00000000 --- a/Controllers/Frontend/AdyenConfig.php +++ /dev/null @@ -1,81 +0,0 @@ -configuration = $this->get(Configuration::class); - $this->dataConversion = $this->get(DataConversion::class); - $this->enrichedPaymentMeanProvider = $this->get(EnrichedPaymentMeanProvider::class); - $this->paymentMeanCollectionSerializer = $this->get(SwPaymentMeanCollectionSerializer::class); - $this->modules = $this->get('modules'); - } - - public function indexAction(): void - { - $this->Front()->Plugins()->ViewRenderer()->setNoRender(); - $this->Response()->setHeader('Content-Type', 'application/json'); - - try { - $admin = $this->modules->Admin(); - - $enrichedPaymentMethods = ($this->enrichedPaymentMeanProvider)( - PaymentMeanCollection::createFromShopwareArray($admin->sGetPaymentMeans()) - ); - - $sBasket = $this->getBasket(); - - $shop = Shopware()->Shop(); - $orderCurrency = $shop->getCurrency(); - - $adyenConfig = [ - 'status' => 'success', - 'shopLocale' => $this->dataConversion->getISO3166FromLocale($shop->getLocale()->getLocale()), - 'clientKey' => $this->configuration->getClientKey($shop), - 'environment' => $this->configuration->getEnvironment($shop), - 'enrichedPaymentMethods' => ($this->paymentMeanCollectionSerializer)($enrichedPaymentMethods), - 'adyenOrderTotal' => round($sBasket['sAmount'], 2), - 'adyenOrderCurrency' => $sBasket['sCurrencyName'] ?? $orderCurrency - ]; - - $this->Response()->setBody( - JsonUtil::encode($adyenConfig) - ); - } catch (Exception $exception) { - $this->Response()->setBody( - json_encode([ - 'status' => 'error', - 'content' => $exception->getMessage() - ]) - ); - } - - } -} diff --git a/Controllers/Frontend/AdyenDonations.php b/Controllers/Frontend/AdyenDonations.php new file mode 100644 index 00000000..d3668f00 --- /dev/null +++ b/Controllers/Frontend/AdyenDonations.php @@ -0,0 +1,100 @@ +Front()->Plugins()->JsonRequest() + ->setParseInput(); + + parent::initController($request, $response); + } + + /** + * @return void + * @throws Exception + */ + public function preDispatch(): void + { + $this->ajaxResponseSetterPreDispatch(); + $this->errorMessageProvider = $this->get(ErrorMessageProvider::class); + $this->snippets = $this->get('snippets'); + } + + public function getDonationsConfigAction(): void + { + $merchantReference = $this->Request()->get('merchantReference'); + $currencyFactor = Shopware()->Shop()->getCurrency()->getFactor(); + $result = CheckoutAPI::get() + ->donation(Shopware()->Shop()->getId()) + ->getDonationSettings($merchantReference, empty($currencyFactor) ? 1 : $currencyFactor); + + $this->returnAPIResponse($result); + } + + public function makeDonationsAction(): void + { + $params = $this->Request()->getParams(); + + $result = CheckoutAPI::get() + ->donation(Shopware()->Shop()->getId()) + ->makeDonation( + new MakeDonationRequest( + $params['amount']['value'] ?? '', + $params['amount']['currency'] ?? '', + $this->Request()->get('merchantReference') + ) + ); + + if (!$result->isSuccessful()) { + $this->errorMessageProvider->add( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'donations/adyen/fail', + 'Donation failed.' + ) + ); + } + + if ($result->isSuccessful()) { + $this->errorMessageProvider->addSuccessMessage( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'donations/adyen/success', + 'Donation successfully made.' + ) + ); + } + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Frontend/AdyenExpressCheckout.php b/Controllers/Frontend/AdyenExpressCheckout.php new file mode 100644 index 00000000..67362589 --- /dev/null +++ b/Controllers/Frontend/AdyenExpressCheckout.php @@ -0,0 +1,100 @@ +checkoutConfigProvider = $this->get(CheckoutConfigProvider::class); + $this->basketHelper = $this->get(BasketHelper::class); + } + + public function getCheckoutConfigAction(): void + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $productNumber = $this->Request()->get('adyen_article_number'); + + $this->Response()->setBody(json_encode( + $this->checkoutConfigProvider->getExpressCheckoutConfig( + $this->basketHelper->getTotalAmountFor($this->prepareCheckoutController(), $productNumber) + )->toArray() + )); + } + + /** + * Main entry point for express checkout processing when Adyen express checkout payment is confirmed on frontend + * + * @return void + * @throws Exception + */ + public function finishAction(): void + { + $productNumber = $this->Request()->get('adyen_article_number'); + if (!empty($productNumber)) { + $this->basketHelper->forceBasketContentFor($productNumber); + } + + // Finish express checkout with forced payment mean and fresh basket + $paymentMean = Shopware()->Modules()->Admin()->sGetPaymentMean( + Plugin::getPaymentMeanName($this->Request()->getParam('adyen_payment_method')) + ); + Shopware()->Modules()->Admin()->sUpdatePayment( + $paymentMean['id'] ?? '' + ); + + Shopware()->Session()->offsetSet( + 'adyenPaymentMethodStateData', + $this->Request()->get('adyenExpressPaymentMethodStateData') + ); + Shopware()->Session()->offsetSet( + 'adyenIsXHR', + $this->Request()->getParam('isXHR') + ); + + $coController = $this->prepareCheckoutController(); + $coController->preDispatch(); + + // Make sure that checkout session data is updated as if confirm view was rendered + $coController->confirmAction(); + + // Simulate order confirmation button click server-side logic (this will redirect tot the payment URL and standard payment processing logic) + $coController->Request()->setParam('sAGB', true); + $coController->Request()->setParam('esdAgreementChecked', true); + $coController->Request()->setParam('serviceAgreementChecked', true); + $coController->paymentAction(); + } + + private function prepareCheckoutController(): Shopware_Controllers_Frontend_Checkout + { + /** @var Shopware_Controllers_Frontend_Checkout $checkoutController */ + $checkoutController = Enlight_Class::Instance(Shopware_Controllers_Frontend_Checkout::class, [$this->request, $this->response]); + $checkoutController->init(); + $checkoutController->setView($this->View()); + $checkoutController->setContainer($this->container); + $checkoutController->setFront($this->front); + $checkoutController->setRequest($this->request); + $checkoutController->setResponse($this->response); + + return $checkoutController; + } + +} diff --git a/Controllers/Frontend/AdyenPaymentProcess.php b/Controllers/Frontend/AdyenPaymentProcess.php new file mode 100644 index 00000000..52dcda89 --- /dev/null +++ b/Controllers/Frontend/AdyenPaymentProcess.php @@ -0,0 +1,365 @@ +getActionName()) { + $this->Front()->Plugins()->JsonRequest()->setParseInput(); + } + + parent::initController($request, $response); + } + + /** + * @return void + * @throws Exception + */ + public function preDispatch(): void + { + $this->errorMessageProvider = $this->get(ErrorMessageProvider::class); + $this->snippets = $this->get('snippets'); + $this->session = $this->get('session'); + $this->checkoutConfigProvider = $this->get(CheckoutConfigProvider::class); + $this->paymentMeansEnricher = $this->get(PaymentMeansEnricher::class); + } + + /** + * Main entry point for checkout processing of adyen payment + * @return void + * @throws Exception + */ + public function indexAction(): void + { + $paymentMeanName = $this->getPaymentShortName(); + if (!$paymentMeanName) { + $this->setupRedirectResponse(Url::getFrontUrl('checkout', 'shippingPayment')); + + return; + } + + if (!Plugin::isAdyenPaymentMean($paymentMeanName)) { + $this->errorMessageProvider->add( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/unrecognized_payment_method', + 'Unrecognized payment method. Please select valid payment method from the list.' + ) + ); + + $this->setupRedirectResponse(Url::getFrontUrl('checkout', 'shippingPayment')); + + return; + } + + $basketSignature = $this->persistBasket(); + $orderReference = $this->generateOrderReference($basketSignature); + $paymentMethodType = Plugin::getAdyenPaymentType($paymentMeanName); + if ($paymentMeanName === AdyenPayment::STORED_PAYMENT_UMBRELLA_NAME) { + $paymentMean = $this->paymentMeansEnricher->enrichPaymentMean( + $this->getUser()['additional']['payment'], + (string)$this->session->get('adyenStoredPaymentMethodId') + ); + + $paymentMethodType = !empty($paymentMean['adyenPaymentType']) ? $paymentMean['adyenPaymentType'] : $paymentMethodType; + } + + $response = CheckoutAPI::get() + ->paymentRequest(Shopware()->Shop()->getId()) + ->startTransaction(new StartTransactionRequest( + $paymentMethodType, + Amount::fromFloat( + $this->getAmount(), + Currency::fromIsoCode($this->getBasket()['sCurrencyName'] ?? 'EUR') + ), + $orderReference, + Url::getFrontUrl( + 'AdyenPaymentProcess', + 'handleRedirect', + ['signature' => $basketSignature, 'reference' => $orderReference] + ), + (array)json_decode($this->session->offsetGet('adyenPaymentMethodStateData'), true), + [ + 'user' => $this->getUser(), + 'basket' => $this->getBasket(), + ] + )); + + if (!$response->isSuccessful()) { + $this->errorMessageProvider->add( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/payment_processing_error', + 'Your payment could not be processed, please resubmit order.' + ) + ); + + $this->setupRedirectResponse(Url::getFrontUrl('checkout', 'shippingPayment')); + + return; + } + + if (!$response->isAdditionalActionRequired()) { + $this->saveOrder( + $response->getPspReference(), + $orderReference + ); + + $this->setupRedirectResponse(Url::getFrontUrl('checkout', 'finish', ['sUniqueID' => $orderReference])); + + return; + } + + + if ($this->isAjaxRequest()) { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $this->Response()->setBody(json_encode([ + 'action' => $response->getAction(), + 'signature' => $basketSignature, + 'reference' => $orderReference, + ])); + + return; + } + + if ($response->shouldPresentToShopper() || $response->isRecieved()) { + $this->saveOrder( + $response->getPspReference() ?? $orderReference, + $orderReference + ); + + Shopware()->Session()->offsetSet('adyenAction', json_encode($response->getAction())); + + $this->redirect(['controller' => 'checkout', 'action' => 'finish', 'sUniqueID' => $orderReference]); + + return; + } + + $this->view->assign('action', $response->getAction()); + $this->view->assign('basketSignature', $basketSignature); + $this->view->assign('orderReference', $orderReference); + } + + public function handleAdditionalDataAction(): void + { + $this->setupRedirectResponse( + $this->handleAdditionalDataAndGetRedirectUrl($this->Request()->getParams()) + ); + } + + public function handleRedirectAction(): void + { + Logger::logDebug( + 'Received handleRedirectAction request', + 'Integration', + ['request' => $this->Request()->getParams()] + ); + + $this->setupRedirectResponse( + $this->handleAdditionalDataAndGetRedirectUrl($this->Request()->getParams()) + ); + } + + /** + * Gets the checkout configuration for Adyen checkout instance + * + * @return void + * + * @throws Enlight_Exception + * @throws InvalidCurrencyCode + */ + public function getCheckoutConfigAction(): void + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $amount = null; + $amountFromBasket = $this->getAmount(); + if (!empty($amountFromBasket)) { + $currency = Currency::fromIsoCode($this->getBasket()['sCurrencyName'] ?? 'EUR'); + $amount = Amount::fromFloat($amountFromBasket, $currency); + } + + $this->Response()->setBody( + json_encode($this->checkoutConfigProvider->getCheckoutConfig($amount)->toArray()) + ); + } + + public function disableCardDetailsAction(): void + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $recurringToken = $this->Request()->get('recurringToken') ?? ''; + + if ($recurringToken === '' || !$this->Request()->isPost()) { + $this->Response()->setBody( + json_encode( + [ + 'message' => $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/disable_action_error', + 'Disable action could not be processed, invalid request.' + ) + ] + ) + ); + $this->Response()->setHttpResponseCode(400); + + return; + } + + $user = Shopware()->Session()->get('sUserId'); + + if (empty($user)) { + $this->Response()->setBody( + json_encode( + [ + 'message' => $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/user_not_found', + 'Disable action could not be processed, user not found.' + ) + ] + ) + ); + $this->Response()->setHttpResponseCode(400); + + return; + } + + $shop = Shopware()->Shop(); + $disableRequest = new DisableStoredDetailsRequest( + $shop->getHost() . '_' . $shop->getId() . '_' . $user, + $recurringToken + ); + + $result = CheckoutAPI::get()->checkoutConfig(Shopware()->Shop()->getId())->disableStoredDetails($disableRequest); + + if (!$result->isSuccessful()) { + $this->Response()->setBody( + json_encode( + $result->toArray() + ) + ); + $this->Response()->setHttpResponseCode(400); + + return; + } + + $this->Response()->setBody(json_encode(['success' => true])); + } + + private function handleAdditionalDataAndGetRedirectUrl(array $additionalData): string + { + $basketSignature = $this->Request()->get('signature'); + try { + $basket = $this->loadBasketFromSignature($basketSignature); + $this->verifyBasketSignature($basketSignature, $basket); + } catch (Exception $e) { + $this->errorMessageProvider->add( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/payment_processing_error', + 'Your payment coule not be processed, please resubmit order.' + ) + ); + + return Url::getFrontUrl('checkout', 'shippingPayment'); + } + + $response = CheckoutAPI::get() + ->paymentRequest(Shopware()->Shop()->getId()) + ->updatePaymentDetails(array_key_exists('details', $additionalData) ? $additionalData : ['details' => $additionalData]); + + if (!$response->isSuccessful()) { + $this->errorMessageProvider->add( + $this->snippets->getNamespace('frontend/adyen/checkout')->get( + 'payment/adyen/payment_processing_error', + 'Your payment could not be processed, please resubmit order.' + ) + ); + + return Url::getFrontUrl('checkout', 'shippingPayment'); + } + + $orderReference = $this->Request()->get('reference'); + + $this->saveOrder( + $response->getPspReference(), + $orderReference + ); + + return Url::getFrontUrl('checkout', 'finish', ['sUniqueID' => $orderReference]); + } + + private function setupRedirectResponse(string $redirectUrl) + { + if ($this->isAjaxRequest()) { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->Response()->setHeader('Content-Type', 'application/json'); + + $this->Response()->setBody(json_encode(['nextStepUrl' => $redirectUrl])); + + return; + } + + $this->redirect($redirectUrl); + } + + private function isAjaxRequest() + { + return $this->Request()->getParam('isXHR') || $this->session->offsetGet('adyenIsXHR'); + } + + private function generateOrderReference(string $basketSignature): string + { + /** + * We do not want more entropy here because basket signature is actually hash string generated by Shopware and + * we need to be max 50 characters long for Adyen validation to pass (Zip payment has 50 characters limit). + * + * @noinspection NonSecureUniqidUsageInspection + */ + return md5(uniqid("{$basketSignature}_")); + } +} diff --git a/Controllers/Frontend/AdyenWebhook.php b/Controllers/Frontend/AdyenWebhook.php new file mode 100644 index 00000000..349f7585 --- /dev/null +++ b/Controllers/Frontend/AdyenWebhook.php @@ -0,0 +1,67 @@ +Front()->Plugins()->JsonRequest() + ->setParseInput(); + + parent::initController($request, $response); + } + + /** + * Returns a list with actions which should not be validated for CSRF protection + * + * @return string[] + */ + public function getWhitelistedCSRFActions(): array + { + return ['index']; + } + + /** + * Handles webhook request. + * + * @return void + * + * @throws InvalidCurrencyCode + * @throws WebhookConfigDoesntExistException + * @throws AuthenticationException + * @throws HMACKeyValidationException + * @throws InvalidDataException + * @throws MerchantAccountCodeException + */ + public function indexAction(): void + { + $payload = $this->Request()->getParams(); + $result = WebhookAPI::get()->webhookHandler($payload['storeId'] ?? '')->handleRequest($payload); + + $this->returnAPIResponse($result); + } +} diff --git a/Controllers/Frontend/ApplePayMerchantAssociation.php b/Controllers/Frontend/ApplePayMerchantAssociation.php index b0b1f687..2add9308 100644 --- a/Controllers/Frontend/ApplePayMerchantAssociation.php +++ b/Controllers/Frontend/ApplePayMerchantAssociation.php @@ -1,56 +1,11 @@ storageFilesystem = $this->get(StorageFilesystem::class); - $this->merchantAssociationFileInstaller = $this->get(MerchantAssociationFileInstaller::class); - $this->logger = $this->get('adyen_payment.logger'); - } - public function indexAction(): void { $this->Front()->Plugins()->ViewRenderer()->setNoRender(); - - if (!$this->storageFilesystem->storageFileExists()) { - iterator_to_array(($this->merchantAssociationFileInstaller)()); - } - - if (!$this->storageFilesystem->storageFileExists()) { - $this->logger->critical($message = 'Could not serve Adyen ApplePay merchant id association file.'); - $this->Response()->setHeader('Content-Type', 'application/json'); - $this->Response()->setHttpResponseCode(Response::HTTP_FAILED_DEPENDENCY); - $this->Response()->setBody(JsonUtil::encode([ - 'success' => false, - 'details' => $message, - ])); - - return; - } - $this->Response()->setHeader('Content-Type', 'text/plain'); - $this->Response()->setBody( - file_get_contents($this->storageFilesystem->storagePath()) - ); + fpassthru(fopen('https://eu.adyen.link/.well-known/apple-developer-merchantid-domain-association', 'rb')); } } diff --git a/Controllers/Frontend/DisableRecurringToken.php b/Controllers/Frontend/DisableRecurringToken.php deleted file mode 100755 index 48de0331..00000000 --- a/Controllers/Frontend/DisableRecurringToken.php +++ /dev/null @@ -1,86 +0,0 @@ -frontendJsonResponse = $this->get(FrontendJsonResponse::class); - $this->disableTokenRequestHandler = $this->get(DisableTokenRequestHandler::class); - $this->snippets = $this->get('snippets'); - } - - public function disabledAction(): void - { - try { - if (!$this->Request()->isPost()) { - $this->frontendJsonResponse->sendJsonBadRequestResponse( - $this->Front(), - $this->Response(), - $this->snippets->getNamespace('adyen/checkout/error')->get( - 'disableTokenInvalidMethodMessage', - 'Invalid method.', - true - ) - ); - - return; - } - - $recurringToken = $this->Request()->getParams()['recurringToken'] ?? ''; - if ('' === $recurringToken) { - $this->frontendJsonResponse->sendJsonBadRequestResponse( - $this->Front(), - $this->Response(), - $this->snippets->getNamespace('adyen/checkout/error')->get( - 'disableTokenMissingRecurringTokenMessage', - 'Missing recurring token param.', - true - ) - ); - - return; - } - - $result = $this->disableTokenRequestHandler->disableToken($recurringToken, Shopware()->Shop()); - if (!$result->isSuccess()) { - $this->frontendJsonResponse->sendJsonBadRequestResponse( - $this->Front(), - $this->Response(), - $this->snippets->getNamespace('adyen/checkout/error')->get( - $result->message() - ) - ); - - return; - } - - $this->frontendJsonResponse->sendJsonResponse( - $this->Front(), - $this->Response(), - JsonResponse::create(null, Response::HTTP_NO_CONTENT) - ); - } catch (\Exception $e) { - $this->frontendJsonResponse->sendJsonBadRequestResponse($this->Front(), $this->Response(), $e->getMessage()); - } - } -} \ No newline at end of file diff --git a/Controllers/Frontend/Notification.php b/Controllers/Frontend/Notification.php deleted file mode 100755 index 947618b6..00000000 --- a/Controllers/Frontend/Notification.php +++ /dev/null @@ -1,148 +0,0 @@ -Front()->Plugins()->ViewRenderer()->setNoRender(); - $this->events = $this->get('events'); - $this->incomingNotificationsManager = $this->get('AdyenPayment\Components\IncomingNotificationManager'); - $this->logger = $this->get('adyen_payment.logger.notifications'); - $this->authorizationValidator = $this->get('AdyenPayment\Http\Validator\Notification\AuthorizationValidator'); - } - - /** - * @return void - */ - public function postDispatch() - { - $data = $this->View()->getAssign(); - $response = $data['responseData'] ?? null; - if (!$response instanceof JsonResponse) { - $response = NotificationResponseFactory::fromShopwareResponse($this->Request(), $data); - } - $this->Response()->setHeader('Content-type', $response->headers->get('Content-Type'), true); - $this->Response()->setHttpResponseCode($response->getStatusCode()); - $this->Response()->setBody($response->getContent()); - } - - /** - * POST: /notification/adyen - * - * @return void - */ - public function adyenAction() - { - try { - $notifications = $this->getNotificationItems(); - $this->authorizationValidator->validate($notifications); - - if (!$this->saveTextNotification($notifications)) { - $this->View()->assign('[notification save error]'); - return; - } - } catch (AuthorizationException $exception) { - $this->View()->assign('responseData', NotificationResponseFactory::unauthorized($exception->getMessage())); - - return; - } catch (\Exception $exception) { - $this->logger->error($exception->getMessage(), [ - 'trace' => $exception->getTraceAsString(), - 'previous' => $exception->getPrevious(), - ]); - - $this->View()->assign('responseData', NotificationResponseFactory::badRequest($exception->getMessage())); - return; - } - - // on valid credentials, always return ACCEPTED - $this->View()->assign('responseData', NotificationResponseFactory::accepted()); - } - - /** - * Whitelist notifyAction - * - * @return string[] - * - * @psalm-return array{0: 'adyen'} - */ - public function getWhitelistedCSRFActions() - { - return ['adyen']; - } - - /** - * @return array|mixed - * @throws Enlight_Event_Exception - */ - private function getNotificationItems() - { - $rawBody = $this->Request()->getRawBody(); - if (empty($rawBody)) { - throw InvalidRequestPayloadException::missingBody(); - } - - $jsonbody = json_decode($rawBody, true); - if (!is_array($jsonbody)) { - throw InvalidRequestPayloadException::invalidBody(); - } - - $notificationItems = $jsonbody['notificationItems'] ?? []; - if (!$notificationItems) { - return []; - } - - $this->events->notify( - Event::NOTIFICATION_RECEIVE, - [ - 'items' => $notificationItems, - ] - ); - - return $notificationItems; - } - - /** - * @param array $notifications - * - * @throws Enlight_Event_Exception - */ - private function saveTextNotification(array $notifications): bool - { - $notifications = $this->events->filter( - Event::NOTIFICATION_SAVE_FILTER_NOTIFICATIONS, - $notifications - ); - - return iterator_count($this->incomingNotificationsManager->saveTextNotification($notifications)) === 0; - } -} diff --git a/Controllers/Frontend/Process.php b/Controllers/Frontend/Process.php deleted file mode 100755 index e3064d0d..00000000 --- a/Controllers/Frontend/Process.php +++ /dev/null @@ -1,216 +0,0 @@ -adyenManager = $this->get(AdyenManager::class); - $this->adyenCheckout = $this->get(PaymentMethodService::class); - $this->basketService = $this->get(BasketService::class); - $this->orderMailService = $this->get(OrderMailService::class); - $this->logger = $this->get('adyen_payment.logger'); - $this->orderManager = $this->get(OrderManager::class); - $this->snippets = $this->get('snippets'); - $this->errorMessageProvider = $this->get(ErrorMessageProvider::class); - } - - /** - * @throws Exception - */ - public function returnAction(): void - { - $this->Front()->Plugins()->ViewRenderer()->setNoRender(); - - $response = $this->Request()->getParams(); - - if ($response) { - $merchantReference = !empty($response['merchantReference']) ? $response['merchantReference'] : ''; - - $order = $this->getOrderByMerchantReference($merchantReference); - $result = $this->validateResponse($response, $order); - - // Make the best effort to obtain merchant reference - if (empty($merchantReference) && !empty($result['merchantReference'])) { - $merchantReference = $result['merchantReference']; - } - - // Make the best effort to obtain related shop order - if (!$order && !empty($merchantReference)) { - $order = $this->getOrderByMerchantReference($merchantReference); - } - - $this->handleReturnResult($result, $order); - - switch(PaymentResultCode::load($result['resultCode'])) { - case PaymentResultCode::authorised(): - case PaymentResultCode::pending(): - case PaymentResultCode::received(): - if (!empty($merchantReference)) { - $this->orderMailService->sendOrderConfirmationMail($merchantReference); - } - $this->redirect([ - 'controller' => 'checkout', - 'action' => 'finish', - 'sUniqueID' => $order ? $order->getTemporaryId() : '', - 'sAGB' => true, - ]); - break; - case PaymentResultCode::cancelled(): - case PaymentResultCode::error(): - case PaymentResultCode::refused(): - default: - $this->errorMessageProvider->add( - $this->snippets->getNamespace('adyen/checkout/error') - ->get('errorTransaction'.$result['resultCode'], $result['refusalReason'] ?? '') - ); - - if (!empty($merchantReference)) { - $this->basketService->cancelAndRestoreByOrderNumber($merchantReference); - } - - $this->redirect([ - 'controller' => 'checkout', - 'action' => 'confirm', - ]); - break; - } - - if ($order) { - $this->orderManager->save($order); - } - } - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - * @throws \Doctrine\ORM\TransactionRequiredException - */ - private function handleReturnResult(array $result, ?Order $order): void - { - if (!$order) { - $this->logger->error('No order found for ', [ - 'ordernumber' => $result['merchantReference'] ?? '', - ]); - - return; - } - - switch (PaymentResultCode::load($result['resultCode'])) { - case PaymentResultCode::authorised(): - case PaymentResultCode::pending(): - case PaymentResultCode::received(): - $paymentStatus = $this->getModelManager()->find( - Status::class, - Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED - ); - break; - case PaymentResultCode::cancelled(): - case PaymentResultCode::error(): - case PaymentResultCode::refused(): - $paymentStatus = $this->getModelManager()->find( - Status::class, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED - ); - break; - default: - $paymentStatus = $this->getModelManager()->find(Status::class, Status::PAYMENT_STATE_REVIEW_NECESSARY); - break; - } - - $this->orderManager->updatePayment( - $order, - (string) ($result['pspReference'] ?? ''), - $paymentStatus - ); - } - - /** - * Validates the payload from checkout /payments hpp and returns the api response - * - * @return mixed - */ - private function validateResponse(array $response, ?Order $order) - { - try { - $request = [ - 'details' => RequestDataFormatter::forPaymentDetails($response), - ]; - - $paymentData = $this->adyenManager->fetchOrderPaymentData($order); - if (!empty($paymentData)) { - $request['paymentData'] = $paymentData; - } - - $checkout = $this->adyenCheckout->getCheckout(); - $response = $checkout->paymentsDetails($request); - } catch (AdyenException $e) { - $response['error'] = $e->getMessage(); - } - - return $response; - } - - private function getOrderByMerchantReference($merchantReference): ?Order - { - return $this->getModelManager()->getRepository(Order::class)->findOneBy([ - 'number' => $merchantReference, - ]); - } -} diff --git a/Dbal/BasketDetailAttributes.php b/Dbal/BasketDetailAttributes.php deleted file mode 100755 index 672dc035..00000000 --- a/Dbal/BasketDetailAttributes.php +++ /dev/null @@ -1,54 +0,0 @@ -db = $db; - } - - /** - * @throws Zend_Db_Adapter_Exception - */ - public function update(int $basketDetailId, array $attributeValues): int - { - return $this->db->update( - 's_order_basket_attributes', - $attributeValues, - ['basketID = ?' => $basketDetailId] - ); - } - - /** - * @throws Zend_Db_Adapter_Exception - */ - public function insert(int $basketDetailId, array $attributeValues): int - { - return $this->db->insert( - 's_order_basket_attributes', - array_merge($attributeValues, ['basketID' => $basketDetailId]) - ); - } - - public function hasBasketDetails(int $basketDetailId): bool - { - return count( - $this->db - ->select() - ->from('s_order_basket_attributes') - ->where('basketID=?', $basketDetailId) - ->query() - ->fetchAll() - ) > 0; - } -} diff --git a/Dbal/OrderDetailAttributes.php b/Dbal/OrderDetailAttributes.php deleted file mode 100755 index d2378330..00000000 --- a/Dbal/OrderDetailAttributes.php +++ /dev/null @@ -1,30 +0,0 @@ -db = $db; - } - - public function fetchByOrderDetailId(string $orderDetailId): array - { - $orderDetailAttributesResult = $this->db - ->select() - ->from('s_order_details_attributes') - ->where('detailID=?', $orderDetailId) - ->query() - ->fetchAll(); - - return $orderDetailAttributesResult[0] ?? []; - } -} diff --git a/Dbal/Remover/PaymentMeanSubShopRemover.php b/Dbal/Remover/PaymentMeanSubShopRemover.php deleted file mode 100755 index 41c269d4..00000000 --- a/Dbal/Remover/PaymentMeanSubShopRemover.php +++ /dev/null @@ -1,25 +0,0 @@ -db = $db; - } - - public function removeBySubShopId(int $subShopId): void - { - $this->db->executeQuery('DELETE FROM s_core_paymentmeans_subshops WHERE subshopID = :subShopId;', [ - ':subShopId' => $subShopId, - ]); - } -} diff --git a/Dbal/Remover/PaymentMeanSubShopRemoverInterface.php b/Dbal/Remover/PaymentMeanSubShopRemoverInterface.php deleted file mode 100644 index 4cfa5402..00000000 --- a/Dbal/Remover/PaymentMeanSubShopRemoverInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -db = $db; - } - - public function registerAdyenPaymentMethodForSubShop(int $subShopId): void - { - $this->db->executeQuery( - 'REPLACE INTO s_core_paymentmeans_subshops (paymentID, subshopID) - SELECT id as paymentID, :subShopId as subshopID - FROM s_core_paymentmeans - WHERE s_core_paymentmeans.source = :adyenSource;', - [ - ':subShopId' => $subShopId, - ':adyenSource' => SourceType::adyen()->getType(), - ] - ); - } -} diff --git a/Dbal/Writer/Payment/PaymentMeansSubShopsWriterInterface.php b/Dbal/Writer/Payment/PaymentMeansSubShopsWriterInterface.php deleted file mode 100644 index 531e76e3..00000000 --- a/Dbal/Writer/Payment/PaymentMeansSubShopsWriterInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -dataPersister = $dataPersister; - $this->attributeUpdater = $attributeUpdater; - } - - public function __invoke(int $paymentMeanId, PaymentMethod $adyenPaymentMethod): void - { - $attributesColumns = [AdyenPayment::ADYEN_CODE => TypeMapping::TYPE_STRING]; - - $dataPersister = $this->dataPersister; - $this->attributeUpdater->writeReadOnlyAttributes( - $table = 's_core_paymentmeans_attributes', - $attributesColumns, - static function() use ($dataPersister, $table, $paymentMeanId, $adyenPaymentMethod) { - return $dataPersister->persist( - [ - '_table' => $table, - '_foreignKey' => $paymentMeanId, - AdyenPayment::ADYEN_CODE => $adyenPaymentMethod->code(), - ], - 's_core_paymentmeans_attributes', - $paymentMeanId - ); - } - ); - } -} diff --git a/Doctrine/Writer/PaymentAttributeWriterInterface.php b/Doctrine/Writer/PaymentAttributeWriterInterface.php deleted file mode 100644 index 5e0d9575..00000000 --- a/Doctrine/Writer/PaymentAttributeWriterInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -entityManager = $entityManager; - $this->paymentFactory = $paymentFactory; - $this->paymentAttributeWriter = $paymentAttributeWriter; - $this->paymentRepository = $paymentRepository; - } - - public function __invoke(PaymentMethod $adyenPaymentMethod, Shop $shop): ImportResult - { - $payment = $this->providePaymentModel($adyenPaymentMethod, $shop); - if ($this->paymentExists($payment)) { - return ImportResult::fromException( - $shop, - $adyenPaymentMethod, - PaymentExistsException::withName($payment->getName()) - ); - } - - $this->entityManager->persist($payment); - $this->entityManager->flush($payment); - if (null === $payment->getId()) { - return ImportResult::fromException( - $shop, - $adyenPaymentMethod, - PaymentNotImportedException::forPayment($adyenPaymentMethod, $payment, $shop) - ); - } - - ($this->paymentAttributeWriter)($payment->getId(), $adyenPaymentMethod); - - return ImportResult::success($shop, $adyenPaymentMethod, ImportStatus::created()); - } - - private function paymentExists(Payment $payment): bool - { - if (null === $payment->getId()) { - return $this->paymentRepository->existsByName($payment->getName()); - } - - return $this->paymentRepository->existsDuplicate($payment); - } - - private function providePaymentModel(PaymentMethod $adyenPaymentMethod, Shop $shop): Payment - { - $swPayment = $this->paymentRepository->findByCode($adyenPaymentMethod->code()); - if (!$swPayment) { - return $this->paymentFactory->createFromAdyen($adyenPaymentMethod, $shop); - } - - return $this->paymentFactory->updateFromAdyen($swPayment, $adyenPaymentMethod, $shop); - } -} diff --git a/Doctrine/Writer/PaymentMethodWriterInterface.php b/Doctrine/Writer/PaymentMethodWriterInterface.php deleted file mode 100644 index 5685b9c7..00000000 --- a/Doctrine/Writer/PaymentMethodWriterInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -paymentRepository = $paymentRepository; - $this->paymentMethodWriter = $paymentMethodWriter; - $this->paymentAttributeWriter = $paymentAttributeWriter; - $this->logger = $logger; - } - - public function __invoke(PaymentMethod $adyenPaymentMethod, Shop $shop): ImportResult - { - if ($adyenPaymentMethod->adyenType()->type() === $adyenPaymentMethod->code()) { - return ($this->paymentMethodWriter)($adyenPaymentMethod, $shop); - } - - // legacy code had adyen 'type' stored as code - $paymentMean = $this->paymentRepository->findByCode($adyenPaymentMethod->adyenType()->type()); - if (!$paymentMean) { - return ($this->paymentMethodWriter)($adyenPaymentMethod, $shop); - } - - $this->log($paymentMean, $adyenPaymentMethod); - ($this->paymentAttributeWriter)($paymentMean->getId(), $adyenPaymentMethod); - - return ($this->paymentMethodWriter)($adyenPaymentMethod, $shop); - } - - private function log(Payment $paymentMean, PaymentMethod $adyenPaymentMethod): void - { - $this->logger->notice(sprintf('Updating legacy payment mean adyen "%s" to "%s"', - $adyenPaymentMethod->adyenType()->type(), - $adyenPaymentMethod->code() - ), [ - 'shopware payment mean' => [ - 'id' => $paymentMean->getId(), - 'name' => $paymentMean->getName(), - 'additional description' => $paymentMean->getAdditionalDescription(), - ], - 'adyen payment method' => [ - 'name' => $adyenPaymentMethod->name(), - 'type' => $adyenPaymentMethod->adyenType()->type(), - 'unique identifier' => $adyenPaymentMethod->code(), - ], - ]); - } -} diff --git a/Enricher/Payment/PaymentMethodEnricher.php b/Enricher/Payment/PaymentMethodEnricher.php deleted file mode 100755 index 9b10042e..00000000 --- a/Enricher/Payment/PaymentMethodEnricher.php +++ /dev/null @@ -1,84 +0,0 @@ -snippets = $snippets; - $this->imageLogoProvider = $imageLogoProvider; - } - - public function __invoke(array $shopwareMethod, PaymentMethod $paymentMethod): array - { - return array_merge($shopwareMethod, [ - 'enriched' => true, - 'additionaldescription' => $this->enrichAdditionalDescription($shopwareMethod, $paymentMethod), - 'image' => $this->imageLogoProvider->provideByType($paymentMethod->adyenType()->type()), - 'isStoredPayment' => $paymentMethod->isStoredPayment(), - 'isAdyenPaymentMethod' => true, - 'adyenType' => $paymentMethod->adyenType()->type(), - 'metadata' => $paymentMethod->rawData(), - ], - $this->enrichStoredPaymentMethodData($shopwareMethod, $paymentMethod) - ); - } - - private function enrichAdditionalDescription(array $shopwareMethod, PaymentMethod $adyenMethod): string - { - $additionalDescription = $shopwareMethod['additionaldescription'] ?? ''; - - if ('' === $additionalDescription) { - $additionalDescription = $this->snippets - ->getNamespace('adyen/method/description') - ->get($shopwareMethod['attribute']['adyen_type'] ?? '') ?? ''; - } - - if (!$adyenMethod->isStoredPayment()) { - return $additionalDescription; - } - - return sprintf( - '%s%s: %s', - ($additionalDescription ? $additionalDescription.' ' : ''), - $this->snippets - ->getNamespace('adyen/checkout/payment') - ->get('CardNumberEndingOn', 'Card number ending on', true), - $adyenMethod->getValue('lastFour', '') - ); - } - - private function enrichStoredPaymentMethodData(array $shopwareMethod, PaymentMethod $paymentMethod): array - { - if (!$paymentMethod->isStoredPayment()) { - return []; - } - - return [ - 'stored_method_umbrella_id' => sprintf( - '%s_%s', - $shopwareMethod['id'], - $paymentMethod->getStoredPaymentMethodId() - ), - 'stored_method_id' => $paymentMethod->getStoredPaymentMethodId(), - 'description' => $paymentMethod->getValue('name'), - 'source' => SourceType::adyen()->getType(), - ]; - } -} diff --git a/Enricher/Payment/PaymentMethodEnricherInterface.php b/Enricher/Payment/PaymentMethodEnricherInterface.php deleted file mode 100644 index b7d289a8..00000000 --- a/Enricher/Payment/PaymentMethodEnricherInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -timestamp; + } + + /** + * @param string $timestamp + */ + public function setTimestamp(string $timestamp): void + { + $this->timestamp = $timestamp; + } +} diff --git a/Exceptions/AuthorizationException.php b/Exceptions/AuthorizationException.php deleted file mode 100644 index 6438c218..00000000 --- a/Exceptions/AuthorizationException.php +++ /dev/null @@ -1,9 +0,0 @@ -getId(), - $notification->getOrderId(), - $notification->getPspReference(), - $notification->getStatus(), - $notification->getPaymentMethod(), - $notification->getEventCode(), - $notification->isSuccess(), - $notification->getMerchantAccountCode(), - $notification->getAmountValue(), - $notification->getAmountCurrency() - )); - } -} diff --git a/Exceptions/InvalidAuthenticationException.php b/Exceptions/InvalidAuthenticationException.php deleted file mode 100644 index 9f0805c4..00000000 --- a/Exceptions/InvalidAuthenticationException.php +++ /dev/null @@ -1,18 +0,0 @@ -getMessage(), $exception->getCode(), $exception); - } -} diff --git a/Exceptions/InvalidParameterException.php b/Exceptions/InvalidParameterException.php deleted file mode 100644 index b8978f77..00000000 --- a/Exceptions/InvalidParameterException.php +++ /dev/null @@ -1,16 +0,0 @@ -getId(), - $swPayment->getName(), - $adyenPaymentMethod->adyenType()->type(), - $adyenPaymentMethod->isStoredPayment() ? ', stored payment id: "'.$adyenPaymentMethod->getStoredPaymentMethodId().'"' : '', - $shop->getId(), - $shop->getName() - )); - } -} diff --git a/Exceptions/RecurringPaymentTokenNotFoundException.php b/Exceptions/RecurringPaymentTokenNotFoundException.php deleted file mode 100644 index af6381b3..00000000 --- a/Exceptions/RecurringPaymentTokenNotFoundException.php +++ /dev/null @@ -1,28 +0,0 @@ -resultCode(), - $pspReference - )); - } -} diff --git a/Exceptions/RecurringPaymentTokenNotSavedException.php b/Exceptions/RecurringPaymentTokenNotSavedException.php deleted file mode 100644 index 28839b50..00000000 --- a/Exceptions/RecurringPaymentTokenNotSavedException.php +++ /dev/null @@ -1,15 +0,0 @@ -identifier().'"'); - } -} diff --git a/Exceptions/StoreDoesNotExistException.php b/Exceptions/StoreDoesNotExistException.php new file mode 100644 index 00000000..048a794d --- /dev/null +++ b/Exceptions/StoreDoesNotExistException.php @@ -0,0 +1,15 @@ +Plugins()->ViewRenderer()->setNoRender(); - - $httpResponse->setHeader('Content-type', $response->headers->get('Content-Type'), true); - $httpResponse->setHttpResponseCode($response->getStatusCode()); - $httpResponse->setBody($response->getContent()); - - return $httpResponse; - } - - public function sendJsonBadRequestResponse( - Enlight_Controller_Front $frontController, - Enlight_Controller_Response_ResponseHttp $httpResponse, - string $message - ): Enlight_Controller_Response_ResponseHttp { - return $this->sendJsonResponse( - $frontController, - $httpResponse, - JsonResponse::create( - ['error' => true, 'message' => $message], - Response::HTTP_BAD_REQUEST - ) - ); - } -} diff --git a/Http/Response/NotificationResponseFactory.php b/Http/Response/NotificationResponseFactory.php deleted file mode 100644 index 7ec2b398..00000000 --- a/Http/Response/NotificationResponseFactory.php +++ /dev/null @@ -1,46 +0,0 @@ - false, - 'message' => $message, - ], Response::HTTP_UNAUTHORIZED); - } - - public static function badRequest(string $message): JsonResponse - { - return new JsonResponse([ - 'success' => false, - 'message' => $message, - ], Response::HTTP_BAD_REQUEST); - } - - public static function fromShopwareResponse(Enlight_Controller_Request_RequestHttp $request, $data): JsonResponse - { - $pretty = (bool) $request->getParam('pretty', false); - if (true === $pretty) { - return new JsonResponse(Zend_Json::prettyPrint($data)); - } - - return new JsonResponse(Zend_Json::encode(array_map(static function($value) { - return $value instanceof \DateTimeInterface ? $value->format(\DateTime::ISO8601) : $value; - }, $data))); - } -} diff --git a/Http/Validator/Notification/AuthenticationValidator.php b/Http/Validator/Notification/AuthenticationValidator.php deleted file mode 100755 index 963b39df..00000000 --- a/Http/Validator/Notification/AuthenticationValidator.php +++ /dev/null @@ -1,38 +0,0 @@ -configuration = $configuration; - } - - /** - * @throws InvalidAuthenticationException - */ - public function validate(array $notifications): void - { - $authUsername = $_SERVER['PHP_AUTH_USER'] ?? $_SERVER['HTTP_PHP_AUTH_USER'] ?? ''; - $authPassword = $_SERVER['PHP_AUTH_PW'] ?? $_SERVER['HTTP_PHP_AUTH_PW'] ?? ''; - - if (!$authUsername || !$authPassword) { - throw InvalidAuthenticationException::missingAuthentication(); - } - - if ($this->configuration->getNotificationAuthUsername() !== $authUsername - || $this->configuration->getNotificationAuthPassword() !== $authPassword - ) { - throw InvalidAuthenticationException::invalidCredentials(); - } - } -} diff --git a/Http/Validator/Notification/Chain.php b/Http/Validator/Notification/Chain.php deleted file mode 100755 index 93dedd77..00000000 --- a/Http/Validator/Notification/Chain.php +++ /dev/null @@ -1,26 +0,0 @@ -validators = $validators; - } - - /** - * {@inheritdoc} - */ - public function validate(array $notifications): void - { - foreach ($this->validators as $validator) { - $validator->validate($notifications); - } - } -} diff --git a/Http/Validator/Notification/HmacValidator.php b/Http/Validator/Notification/HmacValidator.php deleted file mode 100755 index 17da333b..00000000 --- a/Http/Validator/Notification/HmacValidator.php +++ /dev/null @@ -1,46 +0,0 @@ -hmacSignatureService = $hmacSignatureService; - $this->configuration = $configuration; - } - - /** - * @throws InvalidHmacException - */ - public function validate(array $notifications): void - { - foreach ($notifications as $notificationItem) { - try { - $params = $notificationItem['NotificationRequestItem'] ?? []; - $hmacCheck = $this->hmacSignatureService->isValidNotificationHMAC( - $this->configuration->getNotificationHmac(), - $params - ); - if (!$hmacCheck) { - throw InvalidHmacException::withHmacKey($params['additionalData']['hmacSignature'] ?? ''); - } - } catch (AdyenException $exception) { - throw InvalidHmacException::fromAdyenException($exception); - } - } - } -} diff --git a/Http/Validator/Notification/LoggingAuthorizationValidatorDecorator.php b/Http/Validator/Notification/LoggingAuthorizationValidatorDecorator.php deleted file mode 100755 index 5f0cc7ea..00000000 --- a/Http/Validator/Notification/LoggingAuthorizationValidatorDecorator.php +++ /dev/null @@ -1,37 +0,0 @@ -authorizationValidator = $authenticationValidator; - $this->logger = $logger; - } - - public function validate(array $notifications): void - { - try { - $this->authorizationValidator->validate($notifications); - } catch (AuthorizationException $exception) { - $this->logger->critical($exception->getMessage(), [ - 'trace' => $exception->getTraceAsString(), - 'previous' => $exception->getPrevious(), - ]); - - throw $exception; - } - } -} diff --git a/Http/Validator/Notification/NotificationValidatorInterface.php b/Http/Validator/Notification/NotificationValidatorInterface.php deleted file mode 100644 index 0ceddd1a..00000000 --- a/Http/Validator/Notification/NotificationValidatorInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -paymentMethodsProvider = $paymentMethodsProvider; - $this->shopRepository = $shopRepository; - $this->usedFallbackConfigRule = $usedFallbackConfigRule; - $this->paymentMethodWriter = $paymentMethodWriter; - $this->paymentMeansSubShopsWriter = $paymentMeansSubShopsWriter; - } - - public function importAll(): \Generator - { - /** @var Shop $shop */ - foreach ($this->shopRepository->findAll() as $shop) { - if (($this->usedFallbackConfigRule)($shop->getId())) { - $this->paymentMeansSubShopsWriter->registerAdyenPaymentMethodForSubShop($shop->getId()); - yield ImportResult::successSubShopFallback($shop, ImportStatus::updated()); - - continue; - } - - yield from $this->import($shop); - } - } - - public function importForShop(Shop $shop): \Generator - { - yield from $this->import($shop); - } - - /** - * @psalm-return \Generator - */ - private function import(Shop $shop): \Generator - { - $paymentMethods = ($this->paymentMethodsProvider)($shop); - foreach ($paymentMethods as $adyenPaymentMethod) { - try { - yield $this->paymentMethodWriter->__invoke($adyenPaymentMethod, $shop); - } catch (\Exception $exception) { - yield ImportResult::fromException($shop, $adyenPaymentMethod, $exception); - } - } - } -} diff --git a/Import/PaymentMethodImporterInterface.php b/Import/PaymentMethodImporterInterface.php deleted file mode 100644 index dc5e78fb..00000000 --- a/Import/PaymentMethodImporterInterface.php +++ /dev/null @@ -1,20 +0,0 @@ -|ImportResult[] - */ - public function importAll(): \Generator; - /** - * @return \Generator|ImportResult[] - */ - public function importForShop(Shop $shop): \Generator; -} diff --git a/Import/TraceablePaymentMethodImporter.php b/Import/TraceablePaymentMethodImporter.php deleted file mode 100755 index 1635799e..00000000 --- a/Import/TraceablePaymentMethodImporter.php +++ /dev/null @@ -1,68 +0,0 @@ -paymentMethodImporter = $paymentMethodImporter; - $this->logger = $logger; - } - - public function importAll(): \Generator - { - foreach ($this->paymentMethodImporter->importAll() as $importResult) { - $this->log($importResult); - - yield $importResult; - } - } - - public function importForShop(Shop $shop): \Generator - { - foreach ($this->paymentMethodImporter->importForShop($shop) as $importResult) { - $this->log($importResult); - - yield $importResult; - } - } - - private function log(ImportResult $importResult): void - { - $paymentMethod = $importResult->getPaymentMethod(); - if ($importResult->isSuccess()) { - $this->logger->info('Adyen payment method imported', [ - 'shop id' => $importResult->getShop()->getId(), - 'shop name' => $importResult->getShop()->getName(), - 'payment method' => $paymentMethod ? - $paymentMethod->adyenType()->type().' '.$paymentMethod->name() : - 'all', - ]); - - return; - } - - $exception = $importResult->getException(); - $this->logger->error('Adyen payment method could not be imported', [ - 'shop id' => $importResult->getShop()->getId(), - 'shop name' => $importResult->getShop()->getName(), - 'payment type' => $paymentMethod ? $paymentMethod->adyenType()->type() : 'n/a', - 'payment name' => $paymentMethod ? $paymentMethod->name() : 'n/a', - 'message' => $exception ? $exception->getMessage() : 'n/a', - 'exception' => $exception, - ]); - } -} diff --git a/Models/AdyenEntity.php b/Models/AdyenEntity.php new file mode 100644 index 00000000..19ab00e9 --- /dev/null +++ b/Models/AdyenEntity.php @@ -0,0 +1,18 @@ +id; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param string $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return string + */ + public function getIndex_1() + { + return $this->index_1; + } + + /** + * @param string $index_1 + */ + public function setIndex_1($index_1) + { + $this->index_1 = $index_1; + } + + /** + * @return string + */ + public function getIndex_2() + { + return $this->index_2; + } + + /** + * @param string $index_2 + */ + public function setIndex_2($index_2) + { + $this->index_2 = $index_2; + } + + /** + * @return string + */ + public function getIndex_3() + { + return $this->index_3; + } + + /** + * @param string $index_3 + */ + public function setIndex_3($index_3) + { + $this->index_3 = $index_3; + } + + /** + * @return string + */ + public function getIndex_4() + { + return $this->index_4; + } + + /** + * @param string $index_4 + */ + public function setIndex_4($index_4) + { + $this->index_4 = $index_4; + } + + /** + * @return string + */ + public function getIndex_5() + { + return $this->index_5; + } + + /** + * @param string $index_5 + */ + public function setIndex_5($index_5) + { + $this->index_5 = $index_5; + } + + /** + * @return string + */ + public function getIndex_6() + { + return $this->index_6; + } + + /** + * @param string $index_6 + */ + public function setIndex_6($index_6) + { + $this->index_6 = $index_6; + } + + /** + * @return string + */ + public function getIndex_7() + { + return $this->index_7; + } + + /** + * @param string $index_7 + */ + public function setIndex_7($index_7) + { + $this->index_7 = $index_7; + } + + /** + * @return string + */ + public function getIndex_8() + { + return $this->index_8; + } + + /** + * @param string $index_8 + */ + public function setIndex_8($index_8) + { + $this->index_8 = $index_8; + } + + /** + * @return string + */ + public function getIndex_9(): string + { + return $this->index_9; + } + + /** + * @param string $index_9 + */ + public function setIndex_9($index_9): void + { + $this->index_9 = $index_9; + } + + /** + * @return string + */ + public function getData() + { + return $this->data; + } + + /** + * @param string $data + */ + public function setData($data) + { + $this->data = $data; + } +} diff --git a/Models/Enum/Channel.php b/Models/Enum/Channel.php deleted file mode 100644 index 312ee487..00000000 --- a/Models/Enum/Channel.php +++ /dev/null @@ -1,13 +0,0 @@ -getConstants()); - } -} diff --git a/Models/Enum/PaymentMethod/ImportStatus.php b/Models/Enum/PaymentMethod/ImportStatus.php deleted file mode 100644 index ca585ffb..00000000 --- a/Models/Enum/PaymentMethod/ImportStatus.php +++ /dev/null @@ -1,70 +0,0 @@ -availableStates(), true)) { - throw new \InvalidArgumentException('Invalid import status: "'.$status.'"'); - } - - $this->status = $status; - } - - public function getStatus(): string - { - return $this->status; - } - - public function equals(ImportStatus $importStatus): bool - { - return $importStatus->getStatus() === $this->status; - } - - public static function load(string $status): self - { - return new self($status); - } - - public static function created(): self - { - return new self(self::$CREATED); - } - - public static function updated(): self - { - return new self(self::$UPDATED); - } - - public static function notChanged(): self - { - return new self(self::$NOT_CHANGED); - } - - public static function notHandledStatus(): self - { - return new self(self::$NOT_HANDLED); - } - - private function availableStates(): array - { - return [ - self::$CREATED, - self::$UPDATED, - self::$NOT_CHANGED, - self::$NOT_HANDLED, - ]; - } -} diff --git a/Models/Enum/PaymentMethod/PluginType.php b/Models/Enum/PaymentMethod/PluginType.php deleted file mode 100755 index 24f47289..00000000 --- a/Models/Enum/PaymentMethod/PluginType.php +++ /dev/null @@ -1,59 +0,0 @@ -type = $pluginType; - } - - public function getType(): int - { - return $this->type; - } - - public function equals(PluginType $pluginType): bool - { - return $pluginType->getType() === $this->type; - } - - public static function load(int $pluginType): self - { - return new self($pluginType); - } - - public static function adyenType(): self - { - return new self(self::$ADYEN); - } - - public static function isTypeAllowed(int $pluginType): bool - { - return in_array($pluginType, self::availableTypes(), true); - } - - /** - * @internal - * - * @return string[] - */ - public static function availableTypes(): array - { - return [ - self::$ADYEN, - ]; - } -} diff --git a/Models/Enum/PaymentMethod/SourceType.php b/Models/Enum/PaymentMethod/SourceType.php deleted file mode 100755 index 85e45bce..00000000 --- a/Models/Enum/PaymentMethod/SourceType.php +++ /dev/null @@ -1,50 +0,0 @@ -type = $sourceType; - } - - public function getType(): ?int - { - return $this->type; - } - - public function equals(SourceType $sourceType): bool - { - return $sourceType->getType() === $this->type; - } - - public static function load(?int $sourceType): self - { - return new self($sourceType); - } - - public static function shopwareDefault(): self - { - return new self(self::DEFAULT_PAYMENT); - } - - public static function shopwareSelfCreated(): self - { - return new self(self::SELF_CREATED); - } - - public static function adyen(): self - { - return new self(self::ADYEN); - } -} diff --git a/Models/Event.php b/Models/Event.php deleted file mode 100644 index faf2d234..00000000 --- a/Models/Event.php +++ /dev/null @@ -1,107 +0,0 @@ -availableEventNames(), true)) { - throw new \InvalidArgumentException('Invalid Event name: "'.$name.'"'); - } - - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function equals(Event $event): bool - { - return $event->getName() === $this->name; - } - - public static function load(string $name): self - { - return new self($name); - } - - public static function cronImportPaymentMethods(): self - { - return new self(self::$CRON_IMPORT_PAYMENT_METHODS); - } - - public static function cronProcessNotifications(): self - { - return new self(self::$CRON_PROCESS_NOTIFICATIONS); - } - - /** - * @return string[] - */ - private function availableEventNames(): array - { - return [ - self::$CRON_PROCESS_NOTIFICATIONS, - self::$CRON_IMPORT_PAYMENT_METHODS, - - self::NOTIFICATION_RECEIVE, - self::NOTIFICATION_SAVE_FILTER_NOTIFICATIONS, - self::NOTIFICATION_FIND_HANDLERS, - self::NOTIFICATION_PROCESS, - self::NOTIFICATION_NO_ORDER_FOUND, - self::NOTIFICATION_PROCESS_AUTHORISATION, - self::NOTIFICATION_PROCESS_CANCELLATION, - self::NOTIFICATION_PROCESS_CAPTURE, - self::NOTIFICATION_PROCESS_CAPTURE_FAILED, - self::NOTIFICATION_PROCESS_OFFER_CLOSED, - self::NOTIFICATION_PROCESS_REFUND, - self::NOTIFICATION_PROCESS_REFUND_FAILED, - self::NOTIFICATION_PROCESS_REFUNDED_REVERSED, - self::NOTIFICATION_PROCESS_CHARGEBACK, - self::NOTIFICATION_PROCESS_CHARGEBACK_REVERSED, - self::ORDER_STATUS_CHANGED, - self::ORDER_PAYMENT_STATUS_CHANGED, - self::BASKET_RESTORE_FROM_ORDER, - self::BASKET_BEFORE_PROCESS_ORDER_DETAIL, - self::BASKET_STOPPED_PROCESS_ORDER_DETAIL, - self::BASKET_AFTER_PROCESS_ORDER_DETAIL, - ]; - } -} diff --git a/Models/Feedback/NotificationItemFeedback.php b/Models/Feedback/NotificationItemFeedback.php deleted file mode 100755 index 76164c0b..00000000 --- a/Models/Feedback/NotificationItemFeedback.php +++ /dev/null @@ -1,52 +0,0 @@ -message = $message; - $this->notificationItem = $notificationItem; - } - - public function getMessage(): string - { - return $this->message; - } - - public function setMessage(string $message): NotificationItemFeedback - { - $this->message = $message; - - return $this; - } - - public function getNotificationItem(): array - { - return $this->notificationItem; - } - - public function setNotificationItem(array $notificationItem): NotificationItemFeedback - { - $this->notificationItem = $notificationItem; - - return $this; - } -} diff --git a/Models/Feedback/NotificationProcessorFeedback.php b/Models/Feedback/NotificationProcessorFeedback.php deleted file mode 100755 index 9fbd6c4c..00000000 --- a/Models/Feedback/NotificationProcessorFeedback.php +++ /dev/null @@ -1,65 +0,0 @@ -success = $success; - $this->message = $message; - $this->notification = $notification; - } - - public function isSuccess(): bool - { - return $this->success; - } - - public function setSuccess(bool $success): NotificationProcessorFeedback - { - $this->success = $success; - - return $this; - } - - public function getMessage(): string - { - return $this->message; - } - - public function setMessage(string $message): NotificationProcessorFeedback - { - $this->message = $message; - - return $this; - } - - public function getNotification(): Notification - { - return $this->notification; - } - - public function setNotification(Notification $notification): NotificationProcessorFeedback - { - $this->notification = $notification; - - return $this; - } -} diff --git a/Models/Feedback/TextNotificationItemFeedback.php b/Models/Feedback/TextNotificationItemFeedback.php deleted file mode 100755 index b90314a9..00000000 --- a/Models/Feedback/TextNotificationItemFeedback.php +++ /dev/null @@ -1,52 +0,0 @@ -message = $message; - $this->textNotificationItem = $textNotificationItem; - } - - public function getMessage(): string - { - return $this->message; - } - - public function setMessage(string $message): TextNotificationItemFeedback - { - $this->message = $message; - - return $this; - } - - public function getTextNotificationItem(): array - { - return $this->textNotificationItem; - } - - public function setTextNotificationItem(array $textNotificationItem): TextNotificationItemFeedback - { - $this->textNotificationItem = $textNotificationItem; - - return $this; - } -} diff --git a/Models/Notification.php b/Models/Notification.php deleted file mode 100755 index dd87938b..00000000 --- a/Models/Notification.php +++ /dev/null @@ -1,427 +0,0 @@ -setCreatedAt(new \DateTime('now')); - $this->setUpdatedAt(new \DateTime('now')); - } - - /** - * @return int - */ - public function getId(): int - { - return $this->id; - } - - /** - * @param int $id - * @return Notification - */ - public function setId(int $id): Notification - { - $this->id = $id; - return $this; - } - - /** - * @return Order|null - */ - public function getOrder(): ?Order - { - return $this->order; - } - - /** - * @param Order|null $order - * @return Notification - */ - public function setOrder(Order $order): Notification - { - $this->order = $order; - return $this; - } - - /** - * @return int - */ - public function getOrderId(): int - { - return $this->orderId; - } - - /** - * @param int $orderId - * @return Notification - */ - public function setOrderId(int $orderId): Notification - { - $this->orderId = $orderId; - return $this; - } - - /** - * @return string - */ - public function getPspReference(): string - { - return $this->pspReference; - } - - /** - * @param string $pspReference - * @return Notification - */ - public function setPspReference(string $pspReference): Notification - { - $this->pspReference = $pspReference; - return $this; - } - - /** - * @return \DateTime - */ - public function getCreatedAt(): \DateTime - { - return $this->createdAt; - } - - /** - * @param \DateTime $createdAt - * @return Notification - */ - public function setCreatedAt(\DateTime $createdAt): Notification - { - $this->createdAt = $createdAt; - return $this; - } - - /** - * @return \DateTime - */ - public function getUpdatedAt(): \DateTime - { - return $this->updatedAt; - } - - /** - * @param \DateTime $updatedAt - * @return Notification - */ - public function setUpdatedAt(\DateTime $updatedAt): Notification - { - $this->updatedAt = $updatedAt; - return $this; - } - - /** - * @return \DateTime - */ - public function getScheduledProcessingTime(): \DateTime - { - return $this->scheduledProcessingTime; - } - - /** - * @param \DateTime $scheduledProcessingTime - * @return Notification - */ - public function setScheduledProcessingTime(\DateTime $scheduledProcessingTime): Notification - { - $this->scheduledProcessingTime = $scheduledProcessingTime; - return $this; - } - - /** - * @return string - */ - public function getStatus(): string - { - return $this->status; - } - - /** - * @param string $status - * @return Notification - */ - public function setStatus(string $status): Notification - { - $this->status = $status; - return $this; - } - - /** - * @return null|string - */ - public function getPaymentMethod(): string - { - return $this->paymentMethod; - } - - /** - * @param string $paymentMethod - * @return Notification - */ - public function setPaymentMethod(string $paymentMethod): Notification - { - $this->paymentMethod = $paymentMethod; - return $this; - } - - /** - * @return string - */ - public function getEventCode(): string - { - return $this->eventCode; - } - - /** - * @param string $eventCode - * @return Notification - */ - public function setEventCode(string $eventCode): Notification - { - $this->eventCode = $eventCode; - return $this; - } - - /** - * @return bool - */ - public function isSuccess(): bool - { - return $this->success; - } - - /** - * @param bool $success - * @return Notification - */ - public function setSuccess(bool $success): Notification - { - $this->success = $success; - return $this; - } - - /** - * @return string - */ - public function getMerchantAccountCode(): string - { - return $this->merchantAccountCode; - } - - /** - * @param string $merchantAccountCode - * @return Notification - */ - public function setMerchantAccountCode(string $merchantAccountCode): Notification - { - $this->merchantAccountCode = $merchantAccountCode; - return $this; - } - - /** - * @return float - */ - public function getAmountValue(): float - { - return $this->amountValue; - } - - /** - * @param float $amountValue - * @return Notification - */ - public function setAmountValue(float $amountValue): Notification - { - $this->amountValue = $amountValue; - return $this; - } - - /** - * @return string - */ - public function getAmountCurrency(): string - { - return $this->amountCurrency; - } - - /** - * @param string $amountCurrency - * @return Notification - */ - public function setAmountCurrency(string $amountCurrency): Notification - { - $this->amountCurrency = $amountCurrency; - return $this; - } - - /** - * @return string - */ - public function getErrorDetails(): string - { - return $this->errorDetails; - } - - /** - * @param string $errorDetails - * @return Notification - */ - public function setErrorDetails(string $errorDetails): Notification - { - $this->errorDetails = $errorDetails; - return $this; - } - - /** - * Specify data which should be serialized to JSON - * @link https://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource. - * @since 5.4.0 - */ - public function jsonSerialize() - { - return [ - 'id' => $this->getId(), - 'pspReference' => $this->getPspReference(), - 'createdAt' => $this->getCreatedAt(), - 'updatedAt' => $this->getUpdatedAt(), - 'status' => $this->getStatus(), - 'paymentMethod' => $this->getPaymentMethod(), - 'eventCode' => $this->getEventCode(), - 'success' => $this->isSuccess(), - 'merchantAccountCode' => $this->getMerchantAccountCode(), - 'amountValue' => $this->getAmountValue(), - 'amountCurrency' => $this->getAmountCurrency(), - 'errorDetails' => $this->getErrorDetails(), - 'orderId' => $this->getOrderId() - ]; - } -} diff --git a/Models/NotificationException.php b/Models/NotificationException.php deleted file mode 100755 index 0448316a..00000000 --- a/Models/NotificationException.php +++ /dev/null @@ -1,32 +0,0 @@ -notification = $notification; - - parent::__construct($message, $code, $previous); - } - - public function getNotification(): Notification - { - return $this->notification; - } -} diff --git a/Models/NotificationsEntity.php b/Models/NotificationsEntity.php new file mode 100644 index 00000000..9865a6d7 --- /dev/null +++ b/Models/NotificationsEntity.php @@ -0,0 +1,15 @@ +countryRepository = $countryRepository; - $this->pluginIdProvider = $pluginIdProvider; - } - - public function createFromAdyen(PaymentMethod $paymentMethod, Shop $shop): Payment - { - $new = new Payment(); - $new->setActive(true); - $new->setEsdActive(true); - $new->setName($paymentMethod->code()); - $new->setDescription($paymentMethod->name()); - $new->setAdditionalDescription($this->provideAdditionalDescription($paymentMethod)); - $new->setShops(new ArrayCollection([$shop])); - $new->setSource(SourceType::adyen()->getType()); - $new->setPluginId($this->pluginIdProvider->provideId()); - $new->setCountries(new ArrayCollection( - $this->countryRepository->findAll() - )); - - return $new; - } - - public function updateFromAdyen(Payment $payment, PaymentMethod $paymentMethod, Shop $shop): Payment - { - $payment->setName($paymentMethod->code()); - $payment->setDescription($paymentMethod->name()); - $payment->setAdditionalDescription($this->provideAdditionalDescription($paymentMethod)); - $payment->setShops(new ArrayCollection([$shop])); - $payment->setSource(SourceType::adyen()->getType()); - $payment->setPluginId($this->pluginIdProvider->provideId()); - $payment->setCountries(new ArrayCollection( - $this->countryRepository->findAll() - )); - - return $payment; - } - - private function provideAdditionalDescription(PaymentMethod $paymentMethod): string - { - return self::ADYEN_PREFIX.' '.$paymentMethod->name().' ('.$paymentMethod->adyenType()->type().')'; - } -} diff --git a/Models/Payment/PaymentFactoryInterface.php b/Models/Payment/PaymentFactoryInterface.php deleted file mode 100644 index 4d05cf9c..00000000 --- a/Models/Payment/PaymentFactoryInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -availableGroups(), true)) { - throw new \InvalidArgumentException('Invalid Payment method group: "'.$group.'"'); - } - - $this->group = $group; - } - - public static function default(): self - { - return new self(self::DEFAULT); - } - - public static function stored(): self - { - return new self(self::STORED); - } - - public function group(): string - { - return $this->group; - } - - public function equals(PaymentGroup $group): bool - { - return $this->group === $group->group(); - } - - /** - * @return string[] - */ - private function availableGroups(): array - { - return [ - self::DEFAULT, - self::STORED, - ]; - } -} diff --git a/Models/Payment/PaymentMean.php b/Models/Payment/PaymentMean.php deleted file mode 100755 index 08c0c843..00000000 --- a/Models/Payment/PaymentMean.php +++ /dev/null @@ -1,112 +0,0 @@ -id = (int) ($paymentMean['id'] ?? 0); - $new->source = SourceType::load((int) $paymentMean['source']); - $new->raw = $paymentMean; - $new->enriched = (bool) ($paymentMean['enriched'] ?? false); - $new->adyenType = true === $new->enriched ? PaymentType::load((string) $paymentMean['adyenType']) : null; - - return $new; - } - - public function getId(): int - { - return $this->id; - } - - public function getSource(): SourceType - { - return $this->source; - } - - public function isHidden(): bool - { - return (bool) ($this->raw['hide'] ?? false); - } - - public function getAttribute(): Attribute - { - if (array_key_exists('attribute', $this->raw)) { - return $this->raw['attribute']; - } - - /** For compatibility with Shopware 5.6.0 */ - if (array_key_exists('attributes', $this->raw)) { - return $this->raw['attributes']['core'] ?? new Attribute(); - } - - return new Attribute(); - } - - public function isEnriched(): bool - { - return $this->enriched; - } - - public function getAdyenCode(): string - { - if ($this->getAttribute()->exists(AdyenPayment::ADYEN_CODE)) { - return (string) $this->getAttribute()->get(AdyenPayment::ADYEN_CODE); - } - - return ''; - } - - public function getAdyenStoredMethodId(): string - { - return (string) $this->getValue('stored_method_id', ''); - } - - public function adyenType(): ?PaymentType - { - return $this->adyenType; - } - - /** - * @param mixed|null $fallback - * - * @return mixed|null - */ - public function getValue(string $key, $fallback = null) - { - return $this->raw[$key] ?? $fallback; - } - - public function getRaw(): array - { - return $this->raw; - } - - public function isAdyenSourceType(): bool - { - return $this->source->equals(SourceType::adyen()); - } -} diff --git a/Models/Payment/PaymentMethod.php b/Models/Payment/PaymentMethod.php deleted file mode 100755 index d6c0d512..00000000 --- a/Models/Payment/PaymentMethod.php +++ /dev/null @@ -1,119 +0,0 @@ - */ - private $rawData; - - private function __construct() - { - } - - public static function fromRaw(array $data): self - { - $new = new self(); - $new->code = ''; - $new->group = array_key_exists('id', $data) ? PaymentGroup::stored() : PaymentGroup::default(); - $new->type = PaymentType::load((string) ($data['type'] ?? '')); - $new->rawData = $data; - - return $new; - } - - public function withCode(string $name): self - { - $new = clone $this; - $new->code = mb_strtolower(sprintf('%s_%s', - $this->type->type(), - Sanitize::removeNonWord($name) - )); - - // Standardize credit card code for Sweden. Adyen returns scheme_card instead of scheme_credit_card for SW - if ('scheme_card' === $new->code) { - $new->code = 'scheme_credit_card'; - } - - return $new; - } - - public function code(): string - { - return $this->code; - } - - public function adyenType(): PaymentType - { - return $this->type; - } - - public function group(): PaymentGroup - { - return $this->group; - } - - public function rawData(): array - { - return $this->rawData; - } - - public function name(): string - { - return (string) ($this->rawData['name'] ?? ''); - } - - /** - * shortcut to get value of raw payment data. - * - * @return mixed|null - * - * @psalm-param ''|null $fallback - */ - public function getValue(string $key, ?string $fallback = null) - { - return $this->rawData[$key] ?? $fallback; - } - - public function getStoredPaymentMethodId(): string - { - return (string) ($this->rawData['id'] ?? ''); - } - - public function isStoredPayment(): bool - { - return $this->group()->equals(PaymentGroup::stored()); - } - - /** - * @TODO Adyen Checkout API 68 'details' are removed - */ - public function hasDetails(): bool - { - return array_key_exists('details', $this->rawData) && 0 !== count((array) $this->rawData['details']); - } - - public function serializeMinimalState(): string - { - return Sanitize::escape(json_encode([ - 'type' => $this->adyenType()->type(), - ])); - } -} diff --git a/Models/Payment/PaymentType.php b/Models/Payment/PaymentType.php deleted file mode 100755 index 4db2f787..00000000 --- a/Models/Payment/PaymentType.php +++ /dev/null @@ -1,44 +0,0 @@ -type = $type; - } - - public static function load(string $type): self - { - return new self($type); - } - - public static function googlePay(): self - { - return new self(self::GOOGLE_PAY); - } - - public static function applePay(): self - { - return new self(self::APPLE_PAY); - } - - public function type(): string - { - return $this->type; - } - - public function equals(PaymentType $type): bool - { - return $this->type === $type->type(); - } -} diff --git a/Models/PaymentInfo.php b/Models/PaymentInfo.php deleted file mode 100755 index b76798d2..00000000 --- a/Models/PaymentInfo.php +++ /dev/null @@ -1,309 +0,0 @@ -setCreatedAt(new \DateTime('now')); - $this->setUpdatedAt(new \DateTime('now')); - } - - /** - * @return int - */ - public function getId(): int - { - return $this->id; - } - - /** - * @param int $id - * - * @return static - */ - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - - /** - * @return int - */ - public function getOrderId(): int - { - return $this->orderId; - } - - /** - * @param int $orderId - * - * @return static - */ - public function setOrderId(int $orderId): self - { - $this->orderId = $orderId; - - return $this; - } - - - /** - * @return Order|null - */ - public function getOrder() - { - return $this->order; - } - - /** - * @param Order|null $order - * - * @return static - */ - public function setOrder(Order $order = null): self - { - $this->order = $order; - - return $this; - } - - - /** - * @return string - */ - public function getPspReference(): string - { - return $this->pspReference; - } - - - /** - * @param string $pspReference - * - * @return static - */ - public function setPspReference(string $pspReference): self - { - $this->pspReference = $pspReference; - - return $this; - } - - /** - * @return \DateTime - */ - public function getCreatedAt(): \DateTime - { - return $this->createdAt; - } - - /** - * @param \DateTime $createdAt - * - * @return static - */ - public function setCreatedAt(\DateTime $createdAt): self - { - $this->createdAt = $createdAt; - - return $this; - } - - /** - * @return \DateTime - */ - public function getUpdatedAt(): \DateTime - { - return $this->updatedAt; - } - - /** - * @param \DateTime $updatedAt - * - * @return static - */ - public function setUpdatedAt(\DateTime $updatedAt): self - { - $this->updatedAt = $updatedAt; - - return $this; - } - - /** - * @return string - */ - public function getResultCode(): string - { - return $this->resultCode; - } - - /** - * @param string $resultCode - * - * @return static - */ - public function setResultCode(string $resultCode): self - { - $this->resultCode = $resultCode; - - return $this; - } - - /** - * @return string|null - */ - public function getOrdermailVariables() - { - return $this->ordermailVariables; - } - - /** - * @param string|null $ordermailVariables - * - * @return static - */ - public function setOrdermailVariables($ordermailVariables): self - { - $this->ordermailVariables = $ordermailVariables; - - return $this; - } - - /** - * @return string|null - */ - public function getOrdernumber() - { - return $this->ordernumber; - } - - /** - * @param string|null $ordernumber - * - * @return static - */ - public function setOrderNumber($ordernumber): self - { - $this->ordernumber = $ordernumber; - - return $this; - } - - public function getPaymentData(): string - { - return $this->paymentData; - } - - public function setPaymentData(string $paymentData): self - { - $this->paymentData = $paymentData; - - return $this; - } - - public function getStoredMethodId(): string - { - return (string) $this->storedMethodId; - } - - public function setStoredMethodId(string $storedMethodId): self - { - $this->storedMethodId = $storedMethodId; - - return $this; - } -} diff --git a/Models/PaymentMethod/ImportResult.php b/Models/PaymentMethod/ImportResult.php deleted file mode 100755 index c1d2afaf..00000000 --- a/Models/PaymentMethod/ImportResult.php +++ /dev/null @@ -1,88 +0,0 @@ -shop = $shop; - $new->paymentMethod = $paymentMethod; - $new->success = true; - $new->exception = null; - $new->status = $importStatus; - - return $new; - } - - public static function successSubShopFallback(Shop $shop, ImportStatus $importStatus): self - { - $new = new self(); - $new->shop = $shop; - $new->paymentMethod = null; - $new->success = true; - $new->exception = null; - $new->status = $importStatus; - - return $new; - } - - public static function fromException(Shop $shop, ?PaymentMethod $paymentMethod, \Exception $exception): self - { - $new = new self(); - $new->shop = $shop; - $new->paymentMethod = $paymentMethod; - $new->success = false; - $new->exception = $exception; - $new->status = ImportStatus::notHandledStatus(); - - return $new; - } - - public function getShop(): Shop - { - return $this->shop; - } - - public function getPaymentMethod(): ?PaymentMethod - { - return $this->paymentMethod; - } - - public function isSuccess(): bool - { - return $this->success; - } - - public function getException(): ?\Exception - { - return $this->exception; - } - - public function getStatus(): ImportStatus - { - return $this->status; - } -} diff --git a/Models/PaymentMethodInfo.php b/Models/PaymentMethodInfo.php deleted file mode 100644 index 8590bc88..00000000 --- a/Models/PaymentMethodInfo.php +++ /dev/null @@ -1,50 +0,0 @@ -name = $name; - $this->description = $description; - $this->type = $type; - } - - public static function create(string $name, string $description, string $type): self - { - return new self($name, $description, $type); - } - - public function getName(): string - { - return $this->name; - } - - public function getDescription(): string - { - return $this->description; - } - - public function getType(): string - { - return $this->type; - } -} diff --git a/Models/PaymentResultCode.php b/Models/PaymentResultCode.php deleted file mode 100755 index f6ca0f9c..00000000 --- a/Models/PaymentResultCode.php +++ /dev/null @@ -1,120 +0,0 @@ -resultCode = $resultCode; - } - - public function resultCode(): string - { - return $this->resultCode; - } - - public function equals(PaymentResultCode $paymentResultCode): bool - { - return $paymentResultCode->resultCode() === $this->resultCode; - } - - public static function load(string $resultCode): self - { - return new self($resultCode); - } - - public static function exists(string $resultCode): bool - { - return in_array($resultCode, self::availableResultCodes(), true); - } - - public static function authorised(): self - { - return new self(self::AUTHORISED); - } - - public static function challengeShopper(): self - { - return new self(self::CHALLENGE_SHOPPER); - } - - public static function cancelled(): self - { - return new self(self::CANCELLED); - } - - public static function error(): self - { - return new self(self::ERROR); - } - - public static function invalid(): self - { - return new self(self::INVALID); - } - - public static function identifyShopper(): self - { - return new self(self::IDENTIFY_SHOPPER); - } - - public static function pending(): self - { - return new self(self::PENDING); - } - - public static function received(): self - { - return new self(self::RECEIVED); - } - - public static function redirectShopper(): self - { - return new self(self::REDIRECT_SHOPPER); - } - - public static function refused(): self - { - return new self(self::REFUSED); - } - - /** - * @return array - */ - private static function availableResultCodes(): array - { - return [ - self::AUTHORISED, - self::CANCELLED, - self::CHALLENGE_SHOPPER, - self::ERROR, - self::INVALID, - self::IDENTIFY_SHOPPER, - self::PENDING, - self::RECEIVED, - self::REDIRECT_SHOPPER, - self::REFUSED, - ]; - } -} diff --git a/Models/QueueEntity.php b/Models/QueueEntity.php new file mode 100644 index 00000000..53f39dc4 --- /dev/null +++ b/Models/QueueEntity.php @@ -0,0 +1,21 @@ +setCreatedAt(new \DateTimeImmutable()); - $this->setUpdatedAt(new \DateTimeImmutable()); - } - - public static function create( - TokenIdentifier $id, - string $customerId, - string $recurringDetailReference, - string $pspReference, - string $orderNumber, - PaymentResultCode $resultCode, - int $amountValue, - string $amountCurrency - ): self { - $new = new self(); - $new->id = $id->identifier(); - $new->customerId = $customerId; - $new->recurringDetailReference = $recurringDetailReference; - $new->pspReference = $pspReference; - $new->orderNumber = $orderNumber; - $new->resultCode = $resultCode->resultCode(); - $new->amountValue = $amountValue; - $new->amountCurrency = $amountCurrency; - - return $new; - } - - /** - * @internal - * - * @see RecurringPaymentToken::tokenIdentifier() - */ - public function id(): string - { - return $this->id; - } - - public function tokenIdentifier(): TokenIdentifier - { - return TokenIdentifier::generateFromString($this->id); - } - - public function customerId(): string - { - return $this->customerId; - } - - public function recurringDetailReference(): string - { - return $this->recurringDetailReference; - } - - public function pspReference(): string - { - return $this->pspReference; - } - - public function orderNumber(): string - { - return $this->orderNumber; - } - - /** - * @internal - * - * @see RecurringPaymentToken::resultCode() - */ - public function getResultCode(): string - { - return $this->resultCode; - } - - public function resultCode(): PaymentResultCode - { - return PaymentResultCode::load($this->resultCode); - } - - public function amountValue(): int - { - return $this->amountValue; - } - - public function amountCurrency(): string - { - return $this->amountCurrency; - } - - public function createdAt(): \DateTimeImmutable - { - return $this->createdAt; - } - - public function setCreatedAt(\DateTimeImmutable $createdAt): void - { - $this->createdAt = $createdAt; - } - - public function updatedAt(): \DateTimeImmutable - { - return $this->updatedAt; - } - - public function setUpdatedAt(\DateTimeImmutable $updatedAt): void - { - $this->updatedAt = $updatedAt; - } - - public function isSubscription(): bool - { - return '' === $this->orderNumber(); - } - - public function isOneOffPayment(): bool - { - return '' !== $this->orderNumber(); - } -} diff --git a/Models/RecurringPayment/RecurringProcessingModel.php b/Models/RecurringPayment/RecurringProcessingModel.php deleted file mode 100755 index 3ec448ce..00000000 --- a/Models/RecurringPayment/RecurringProcessingModel.php +++ /dev/null @@ -1,58 +0,0 @@ -availableRecurringProcessingModels(), true)) { - throw new \InvalidArgumentException('Invalid recurring processing model: "'.$recurringProcessingModel.'"'); - } - - $this->recurringProcessingModel = $recurringProcessingModel; - } - - public function recurringProcessingModel(): string - { - return $this->recurringProcessingModel; - } - - public function equals(RecurringProcessingModel $recurringProcessingModel): bool - { - return $recurringProcessingModel->recurringProcessingModel() === $this->recurringProcessingModel; - } - - public static function load(string $recurringProcessingModel): self - { - return new self($recurringProcessingModel); - } - - public static function cardOnFile(): self - { - return new self(self::CARD_ON_FILE); - } - - public static function subscription(): self - { - return new self(self::SUBSCRIPTION); - } - - private function availableRecurringProcessingModels(): array - { - return [ - self::CARD_ON_FILE, - self::SUBSCRIPTION, - self::UNSCHEDULED_CARD_ON_FILE, - ]; - } -} diff --git a/Models/RecurringPayment/ShopperInteraction.php b/Models/RecurringPayment/ShopperInteraction.php deleted file mode 100755 index 9dc82858..00000000 --- a/Models/RecurringPayment/ShopperInteraction.php +++ /dev/null @@ -1,60 +0,0 @@ -availableShopperInteractions(), true)) { - throw new \InvalidArgumentException('Invalid shopper interaction: "'.$shopperInteraction.'"'); - } - - $this->shopperInteraction = $shopperInteraction; - } - - public function shopperInteraction(): string - { - return $this->shopperInteraction; - } - - public function equals(ShopperInteraction $paymentShopperInteraction): bool - { - return $paymentShopperInteraction->shopperInteraction() === $this->shopperInteraction; - } - - public static function load(string $shopperInteraction): self - { - return new self($shopperInteraction); - } - - public static function contAuth(): self - { - return new self(self::CONT_AUTH); - } - - public static function ecommerce(): self - { - return new self(self::ECOMMERCE); - } - - private function availableShopperInteractions(): array - { - return [ - self::CONT_AUTH, - self::ECOMMERCE, - self::MOTO, - self::POS, - ]; - } -} diff --git a/Models/Refund.php b/Models/Refund.php deleted file mode 100755 index 4405177d..00000000 --- a/Models/Refund.php +++ /dev/null @@ -1,167 +0,0 @@ -id; - } - - /** - * @param int $id - * @return Refund - */ - public function setId(int $id): Refund - { - $this->id = $id; - return $this; - } - - /** - * @return int - */ - public function getOrderId(): int - { - return $this->orderId; - } - - /** - * @param int $orderId - * @return Refund - */ - public function setOrderId(int $orderId): Refund - { - $this->orderId = $orderId; - return $this; - } - - /** - * @return Order|null - */ - public function getOrder(): Order - { - return $this->order; - } - - /** - * @param Order|null $order - * @return Refund - */ - public function setOrder(Order $order): Refund - { - $this->order = $order; - return $this; - } - - /** - * @return string - */ - public function getPspReference(): string - { - return $this->pspReference; - } - - /** - * @param string $pspReference - * @return Refund - */ - public function setPspReference(string $pspReference): Refund - { - $this->pspReference = $pspReference; - return $this; - } - - /** - * @return \DateTime - */ - public function getCreatedAt(): \DateTime - { - return $this->createdAt; - } - - /** - * @param \DateTime $createdAt - * @return Refund - */ - public function setCreatedAt(\DateTime $createdAt): Refund - { - $this->createdAt = $createdAt; - return $this; - } - - /** - * @return \DateTime - */ - public function getUpdatedAt(): \DateTime - { - return $this->updatedAt; - } - - /** - * @param \DateTime $updatedAt - * @return Refund - */ - public function setUpdatedAt(\DateTime $updatedAt): Refund - { - $this->updatedAt = $updatedAt; - return $this; - } -} diff --git a/Models/TextNotification.php b/Models/TextNotification.php deleted file mode 100755 index f5718572..00000000 --- a/Models/TextNotification.php +++ /dev/null @@ -1,114 +0,0 @@ -setCreatedAt(new \DateTime('now')); - } - - /** - * @return int - */ - public function getId(): int - { - return $this->id; - } - - /** - * @param int $id - * @return TextNotification - */ - public function setId(int $id): TextNotification - { - $this->id = $id; - return $this; - } - - /** - * @return string - */ - public function getTextNotification(): string - { - return $this->textNotification; - } - - /** - * @param string $textNotification - * @return TextNotification - */ - public function setTextNotification(string $textNotification): TextNotification - { - $this->textNotification = $textNotification; - return $this; - } - - /** - * @return \DateTime - */ - public function getCreatedAt(): \DateTime - { - return $this->createdAt; - } - - /** - * @param \DateTime $createdAt - * @return TextNotification - */ - public function setCreatedAt(\DateTime $createdAt): TextNotification - { - $this->createdAt = $createdAt; - return $this; - } - - /** - * Specify data which should be serialized to JSON - * @link https://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource. - * @since 5.4.0 - */ - public function jsonSerialize() - { - return [ - 'id' => $this->getId(), - 'textNotification' => $this->getTextNotification(), - 'createdAt' => $this->getCreatedAt() - ]; - } -} diff --git a/Models/TokenIdentifier.php b/Models/TokenIdentifier.php deleted file mode 100755 index f7301bd1..00000000 --- a/Models/TokenIdentifier.php +++ /dev/null @@ -1,39 +0,0 @@ -tokenId = $tokenId; - } - - public static function generate(): TokenIdentifier - { - return new self(Uuid::uuid4()); - } - - public static function generateFromString(string $uuid): TokenIdentifier - { - return new self(Uuid::fromString($uuid)); - } - - public function identifier(): string - { - return $this->tokenId->toString(); - } - - public function equals(TokenIdentifier $id): bool - { - return $id->identifier() === $this->identifier(); - } -} diff --git a/Models/TransactionLogEntity.php b/Models/TransactionLogEntity.php new file mode 100644 index 00000000..6f64ff4d --- /dev/null +++ b/Models/TransactionLogEntity.php @@ -0,0 +1,15 @@ +=7.4 -* Shopware >=5.7.3 +* PHP ^7.2 | ^7.4 | ^8.0 +* Shopware >=5.6.0 Note: The Adyen payment plugin is not compatible with the cookie manager plugin (<= 5.6.2), it is however compatible with the Shopware default cookie consent manager (>5.6.2). @@ -23,7 +23,6 @@ Please see our Wiki for the [integration guide](https://github.com/Adyen/adyen-s Please find the relevant documentation for - [Get started with Adyen](https://docs.adyen.com/user-management/get-started-with-adyen) - [Shopware 5 plugin integration guide](https://github.com/Adyen/adyen-shopware5/wiki) - - [Adyen PHP API Library](https://docs.adyen.com/development-resources/libraries#php) ## See [HELP](https://github.com/Adyen/adyen-shopware5/wiki#help) in our Wiki. @@ -32,9 +31,5 @@ Please find the relevant documentation for ## Integration The plugin integrates card component (Secured Fields) using Adyen Checkout for all card payments. -## API Library -This module is using the Adyen's API Library for PHP for all (API) connections to Adyen. -This library can be found here - ## License MIT license. For more information, see the [LICENSE file](LICENSE). diff --git a/Recurring/RecurringTokenFactory.php b/Recurring/RecurringTokenFactory.php deleted file mode 100644 index 02ec8b44..00000000 --- a/Recurring/RecurringTokenFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -entityManager = Shopware()->Container()->get('models'); + } + + /** + * Returns full class name. + * + * @return string Full class name. + */ + public static function getClassName() + { + return static::THIS_CLASS_NAME; + } + + /** + * Sets repository entity. + * + * @param string $entityClass Repository entity class. + */ + public function setEntityClass($entityClass) + { + $this->entityClass = $entityClass; + } + + /** + * Executes select query. + * + * @param QueryFilter $filter Filter for query. + * + * @return Entity[] A list of found entities ot empty array. + * + * @throws QueryFilterInvalidParamException + */ + public function select(QueryFilter $filter = null) + { + $query = $this->getBaseDoctrineQuery($filter); + + return $this->getResult($query); + } + + /** + * Executes select query and returns first result. + * + * @param QueryFilter $filter Filter for query. + * + * @return Entity | null First found entity or NULL. + * + * @throws QueryFilterInvalidParamException + */ + public function selectOne(QueryFilter $filter = null) + { + $query = $this->getBaseDoctrineQuery($filter); + $query->setMaxResults(1); + + $result = $this->getResult($query); + + return !empty($result[0]) ? $result[0] : null; + } + + /** + * Executes insert query and returns ID of created entity. Entity will be updated with new ID. + * + * @param Entity $entity Entity to be saved. + * + * @return int Identifier of saved entity. + * @throws OptimisticLockException + * @throws ORMException + */ + public function save(Entity $entity) + { + $doctrineEntity = new static::$doctrineModel; + $id = $this->persistEntity($entity, $doctrineEntity); + $entity->setId($id); + + return $id; + } + + /** + * Executes update query and returns success flag. + * + * @param Entity $entity Entity to be updated. + * + * @return bool TRUE if operation succeeded; otherwise, FALSE. + */ + public function update(Entity $entity) + { + $result = true; + + try { + /** @var BaseEntity $doctrineEntity */ + $doctrineEntity = $this->entityManager->find(static::$doctrineModel, $entity->getId()); + if ($doctrineEntity) { + $this->persistEntity($entity, $doctrineEntity); + } else { + $result = false; + } + } catch (Exception $e) { + $result = false; + } + + return $result; + } + + /** + * Executes delete query and returns success flag. + * + * @param Entity $entity Entity to be deleted. + * + * @return bool TRUE if operation succeeded; otherwise, FALSE. + */ + public function delete(Entity $entity) + { + $result = true; + + try { + $persistentEntity = $this->entityManager->find(static::$doctrineModel, $entity->getId()); + if ($persistentEntity) { + $this->entityManager->remove($persistentEntity); + $this->entityManager->flush(); + } + } catch (Exception $e) { + $result = false; + } + + return $result; + } + + /** + * Counts records that match filter criteria. + * + * @param QueryFilter|null $filter Filter for query. + * + * @return int Number of records that match filter criteria. + * + * @throws NonUniqueResultException + * @throws QueryFilterInvalidParamException + */ + public function count(QueryFilter $filter = null) + { + $query = $this->getBaseDoctrineQuery($filter, true); + + return (int)$query->getQuery()->getSingleScalarResult(); + } + + /** + * Builds condition groups (each group is chained with OR internally, and with AND externally) based on query + * filter. + * + * @param QueryFilter $filter Query filter object. + * @param array $fieldIndexMap Map of property indexes. + * + * @return array Array of condition groups.. + * + * @throws QueryFilterInvalidParamException + */ + protected function buildConditionGroups(QueryFilter $filter, array $fieldIndexMap): array + { + $groups = []; + $counter = 0; + $fieldIndexMap['id'] = 0; + foreach ($filter->getConditions() as $condition) { + if (!empty($groups[$counter]) && $condition->getChainOperator() === 'OR') { + $counter++; + } + + // Only index columns can be filtered. + if (!array_key_exists($condition->getColumn(), $fieldIndexMap)) { + throw new QueryFilterInvalidParamException("Field [{$condition->getColumn()}] is not indexed."); + } + + $groups[$counter][] = $condition; + } + + return $groups; + } + + /** + * Retrieves doctrine query. + * + * @param QueryFilter|null $filter + * + * @param bool $isCount + * + * @return QueryBuilder + * @throws QueryFilterInvalidParamException + */ + protected function getBaseDoctrineQuery(QueryFilter $filter = null, bool $isCount = false): QueryBuilder + { + /** @var Entity $entity */ + $entity = new $this->entityClass; + $type = $entity->getConfig()->getType(); + $indexMap = IndexHelper::mapFieldsToIndexes($entity); + + $query = $this->entityManager->createQueryBuilder(); + $alias = 'p'; + $baseSelect = $isCount ? "count($alias.id)" : $alias; + $query->select($baseSelect) + ->from(static::$doctrineModel, $alias) + ->where("$alias.type = '$type'"); + + $groups = $filter ? $this->buildConditionGroups($filter, $indexMap) : []; + $queryParts = $this->getQueryParts($groups, $indexMap, $alias); + + $where = $this->generateWhereStatement($queryParts); + if (!empty($where)) { + $query->andWhere($where); + } + + if ($filter) { + $this->setLimit($filter, $query); + $this->setOrderBy($filter, $indexMap, $alias, $query); + $query->setFirstResult($filter->getOffset()); + } + + return $query; + } + + /** + * Retrieves group query parts. + * + * @param array $conditionGroups + * @param array $indexMap + * @param string $alias + * + * @return array + */ + protected function getQueryParts(array $conditionGroups, array $indexMap, string $alias): array + { + $parts = []; + + foreach ($conditionGroups as $group) { + $subPart = []; + + foreach ($group as $condition) { + $subPart[] = $this->getQueryPart($condition, $indexMap, $alias); + } + + if (!empty($subPart)) { + $parts[] = $subPart; + } + } + + return $parts; + } + + /** + * Retrieves query part. + * + * @param QueryCondition $condition + * @param array $indexMap + * @param string $alias + * + * @return string + */ + protected function getQueryPart(QueryCondition $condition, array $indexMap, string $alias): string + { + $column = $condition->getColumn(); + + if ($column === 'id') { + return "$alias.id=" . $condition->getValue(); + } + + $part = "$alias.index_" . $indexMap[$column] . ' ' . $condition->getOperator(); + if (!in_array($condition->getOperator(), array(Operators::NULL, Operators::NOT_NULL), true)) { + if (in_array($condition->getOperator(), array(Operators::NOT_IN, Operators::IN), true)) { + $part .= $this->getInOperatorValues($condition); + } else { + $part .= " '" . IndexHelper::castFieldValue($condition->getValue(), $condition->getValueType()) . "'"; + } + } + + return $part; + } + + /** + * Handles values for the IN and NOT IN operators, + * + * @param QueryCondition $condition + * + * @return string + */ + protected function getInOperatorValues(QueryCondition $condition): string + { + $values = array_map( + function ($item) { + if (is_string($item)) { + return "'$item'"; + } + + return "'" . IndexHelper::castFieldValue($item, is_int($item) ? 'integer' : 'double') . "'"; + }, + $condition->getValue() + ); + + return '(' . implode(',', $values) . ')'; + } + + /** + * Retrieves query result. + * + * @param QueryBuilder $builder + * + * @return Entity[] + */ + protected function getResult(QueryBuilder $builder): array + { + $doctrineEntities = $builder->getQuery()->getResult(); + + $result = []; + + /** @var BaseEntity $doctrineEntity */ + foreach ($doctrineEntities as $doctrineEntity) { + $entity = $this->unserializeEntity($doctrineEntity->getData()); + if ($entity) { + $entity->setId($doctrineEntity->getId()); + $result[] = $entity; + } + } + + return $result; + } + + /** + * Unserializes ORM entity. + * + * @param string $data + * + * @return Entity + */ + protected function unserializeEntity(string $data): Entity + { + $jsonEntity = json_decode($data, true); + if (array_key_exists('class_name', $jsonEntity)) { + $entity = new $jsonEntity['class_name']; + } else { + $entity = new $this->entityClass; + } + + /** @var Entity $entity */ + $entity->inflate($jsonEntity); + + return $entity; + } + + /** + * Persists entity. + * + * @param Entity $entity + * @param BaseEntity $persistedEntity + * + * @return int + * + * @throws OptimisticLockException + * @throws ORMException + */ + protected function persistEntity(Entity $entity, BaseEntity $persistedEntity): int + { + $persistedEntity->setType($entity->getConfig()->getType()); + + $indexValueMap = IndexHelper::transformFieldsToIndexes($entity); + + foreach ($indexValueMap as $index => $value) { + $setterName = "setIndex_{$index}"; + $persistedEntity->$setterName($value); + } + + $persistedEntity->setData(json_encode($entity->toArray())); + + $this->entityManager->persist($persistedEntity); + $this->entityManager->flush($persistedEntity); + + return $persistedEntity->getId(); + } + + /** + * Generates where statement. + * + * @param array $queryParts + * + * @return string + */ + protected function generateWhereStatement(array $queryParts): string + { + $where = ''; + + foreach ($queryParts as $index => $part) { + $subWhere = ''; + + if ($index > 0) { + $subWhere .= ' OR '; + } + + $subWhere .= $part[0]; + $count = count($part); + for ($i = 1; $i < $count; $i++) { + $subWhere .= ' AND ' . $part[$i]; + } + + $where .= $subWhere; + } + + return $where; + } + + /** + * Sets limit. + * + * @param QueryFilter $filter + * @param QueryBuilder $query + */ + protected function setLimit(QueryFilter $filter, QueryBuilder $query): void + { + if ($filter->getLimit()) { + $query->setMaxResults($filter->getLimit()); + } + } + + /** + * Sets order by. + * + * @param QueryFilter $filter + * @param array $indexMap + * @param $alias + * @param QueryBuilder $query + */ + protected function setOrderBy(QueryFilter $filter, array $indexMap, $alias, QueryBuilder $query): void + { + if ($filter->getOrderByColumn()) { + $orderByColumn = $filter->getOrderByColumn(); + + if ($orderByColumn === 'id' || !empty($indexMap[$orderByColumn])) { + $columnName = $orderByColumn === 'id' + ? "$alias.id" : "$alias.index_" . $indexMap[$orderByColumn]; + $query->orderBy($columnName, $filter->getOrderDirection()); + } + } + } +} diff --git a/Repositories/BaseRepositoryWithConditionalDeletes.php b/Repositories/BaseRepositoryWithConditionalDeletes.php new file mode 100644 index 00000000..cdb2e113 --- /dev/null +++ b/Repositories/BaseRepositoryWithConditionalDeletes.php @@ -0,0 +1,52 @@ +entityClass; + $type = $entity->getConfig()->getType(); + $indexMap = IndexHelper::mapFieldsToIndexes($entity); + + $query = $this->entityManager->createQueryBuilder(); + $alias = 'p'; + $query->delete() + ->from(static::$doctrineModel, $alias) + ->where("$alias.type = :type") + ->setParameter('type', $type); + + $groups = $queryFilter ? $this->buildConditionGroups($queryFilter, $indexMap) : []; + $queryParts = $this->getQueryParts($groups, $indexMap, $alias); + + $where = $this->generateWhereStatement($queryParts); + if (!empty($where)) { + $query->andWhere($where); + } + + $query->getQuery()->execute(); + } catch (Exception $e) { + Logger::logError('Delete where failed with error ' . $e->getMessage()); + } + } +} diff --git a/Repositories/NotificationsRepository.php b/Repositories/NotificationsRepository.php new file mode 100644 index 00000000..e753aaff --- /dev/null +++ b/Repositories/NotificationsRepository.php @@ -0,0 +1,18 @@ +Container()->get('dbal_connection'); + + $ids = $this->getQueueIdsForExecution($priority, $limit); + $rawItems = $connection->createQueryBuilder() + ->select('queue.id', 'queue.data') + ->from($this->getDbName(), 'queue') + ->where('id IN(:ids)') + ->setParameter('ids', $ids, Connection::PARAM_INT_ARRAY) + ->orderBy('queue.id') + ->execute() + ->fetchAll(); + + $result = $this->inflateQueueItems(!empty($rawItems) ? $rawItems : []); + } catch (Exception $e) { + // In case of database exception return empty result set. + } + + return $result; + } + + /** + * Creates or updates given queue item. If queue item id is not set, new queue item will be created otherwise + * update will be performed. + * + * @param QueueItem $queueItem Item to save + * @param array $additionalWhere List of key/value pairs that must be satisfied upon saving queue item. Key is + * queue item property and value is condition value for that property. Example for MySql storage: + * $storage->save($queueItem, array('status' => 'queued')) should produce query + * UPDATE queue_storage_table SET .... WHERE .... AND status => 'queued' + * + * @return int Id of saved queue item + * + * @throws OptimisticLockException + * @throws QueryFilterInvalidParamException + * @throws QueueItemSaveException if queue item could not be saved + */ + public function saveWithCondition(QueueItem $queueItem, array $additionalWhere = array()): int + { + if ($queueItem->getId()) { + $this->updateQueueItem($queueItem, $additionalWhere); + + return $queueItem->getId(); + } + + return $this->save($queueItem); + } + + public function batchStatusUpdate(array $ids, $status) + { + /** @var Connection $connection */ + $connection = Shopware()->Container()->get('dbal_connection'); + + $index = $this->getColumnIndexMap(); + $statusColumn = 'index_' . $index['status']; + + $connection->createQueryBuilder() + ->update($this->getDbName(), 'queue') + ->set("queue.$statusColumn", ':status') + ->where('id IN(:ids)') + ->setParameter(':status', $status) + ->setParameter('ids', $ids, Connection::PARAM_INT_ARRAY) + ->execute(); + } + + /** + * Retrieves queue item ids. + * + * @param int $priority + * @param int $limit + * + * @return array + */ + protected function getQueueIdsForExecution(int $priority, int $limit): array + { + /** @var Connection $connection */ + $connection = Shopware()->Container()->get('dbal_connection'); + + $index = $this->getColumnIndexMap(); + $nameColumn = 'index_' . $index['queueName']; + $statusColumn = 'index_' . $index['status']; + $priorityColumn = 'index_' . $index['priority']; + $queuedStatus = QueueItem::QUEUED; + $inProgressStatus = QueueItem::IN_PROGRESS; + + $runningQueueNames = $connection->createQueryBuilder() + ->select("DISTINCT $nameColumn") + ->from($this->getDbName(), 'queue') + ->where("queue.$statusColumn = :status") + ->setParameter(':status', $inProgressStatus) + ->execute() + ->fetchAll(\PDO::FETCH_COLUMN); + + $query = $connection->createQueryBuilder() + ->select('MIN(queue.id) AS id') + ->from($this->getDbName(), 'queue') + ->where("queue.$statusColumn = :status") + ->andWhere("queue.$priorityColumn = :priority") + ->setParameter(':status', $queuedStatus) + ->setParameter(':priority', IndexHelper::castFieldValue($priority, Index::INTEGER)) + ->groupBy("queue.$nameColumn"); + + if (!empty($runningQueueNames)) { + $query + ->andWhere("queue.$nameColumn NOT IN(:names)") + ->setParameter(':names', $runningQueueNames, Connection::PARAM_STR_ARRAY); + } + + $result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + sort($result); + + return array_slice($result, 0, $limit); + } + + /** + * Updates queue item. + * + * @param QueueItem $queueItem + * @param array $additionalWhere + * + * + * @throws QueryFilterInvalidParamException + * @throws QueueItemSaveException + */ + protected function updateQueueItem(QueueItem $queueItem, array $additionalWhere): void + { + $filter = new QueryFilter(); + $filter->where('id', Operators::EQUALS, $queueItem->getId()); + + foreach ($additionalWhere as $name => $value) { + if ($value === null) { + $filter->where($name, Operators::NULL); + } else { + $filter->where($name, Operators::EQUALS, $value); + } + } + + /** @var QueueItem $item */ + $item = $this->selectOne($filter); + if ($item === null) { + throw new QueueItemSaveException("Cannot update queue item with id {$queueItem->getId()}."); + } + + $this->update($queueItem); + } + + /** + * Retrieves index column map. + * + * @return array + */ + protected function getColumnIndexMap(): array + { + $queueItem = new QueueItem(); + + return IndexHelper::mapFieldsToIndexes($queueItem); + } + + /** + * Retrieves db_name for DBAL. + * + * @return string + */ + protected function getDbName(): string + { + return 's_plugin_adyen_queue'; + } + + /** + * Inflates queue items. + * + * @param array $rawItems + * + * @return array + */ + protected function inflateQueueItems(array $rawItems = []): array + { + $result = []; + foreach ($rawItems as $rawItem) { + $item = new QueueItem(); + $item->inflate(json_decode($rawItem['data'], true)); + $item->setId((int)$rawItem['id']); + $result[] = $item; + } + + return $result; + } +} diff --git a/Repositories/TransactionLogRepository.php b/Repositories/TransactionLogRepository.php new file mode 100644 index 00000000..e8b9426c --- /dev/null +++ b/Repositories/TransactionLogRepository.php @@ -0,0 +1,18 @@ +shopwareRepository = $repository; + } + + /** + * @return array + */ + public function getOrderStatuses(): array + { + return $this->shopwareRepository->getPaymentStatusQuery()->getArrayResult(); + } + + /** + * Gets the order numbers for the given list of order temporary ids + * + * @param string[] $temporaryIds + * @return array Map of order temporary id to its belonging order number + */ + public function getOrderNumbersFor(array $temporaryIds): array + { + $query = $this->shopwareRepository->createQueryBuilder('orders'); + $query + ->where('orders.temporaryId IN (:temporaryIds)') + ->setParameter(':temporaryIds', $temporaryIds, Connection::PARAM_STR_ARRAY); + + /** @var ShopwareOrder[] $result */ + $result = $query->getQuery()->getResult(); + + $orderMap = []; + foreach ($result as $order) { + $orderMap[$order->getTemporaryId()] = $order->getNumber(); + } + + return $orderMap; + } + + /** + * Returns a map of Showpare order instances based on a list of order ids + * + * @param string[] $orderIds + * @return ShopwareOrder[] + */ + public function getOrdersByIds(array $orderIds): array + { + $query = $this->shopwareRepository->createQueryBuilder('orders'); + $query + ->where('orders.id IN (:orderIds)') + ->setParameter(':orderIds', $orderIds, Connection::PARAM_INT_ARRAY); + + /** @var ShopwareOrder[] $result */ + $result = $query->getQuery()->getResult(); + + $orderMap = []; + foreach ($result as $order) { + $orderMap[$order->getId()] = $order; + } + + return $orderMap; + } + + /** + * Returns a map of Showpare order instances based on a list of order numbers + * + * @param string[] $orderNumbers + * @return ShopwareOrder[] + */ + public function getOrdersByNumbers(array $orderNumbers): array + { + $query = $this->shopwareRepository->createQueryBuilder('orders'); + $query + ->where('orders.number IN (:orderNumbers)') + ->setParameter(':orderNumbers', $orderNumbers, Connection::PARAM_STR_ARRAY); + + /** @var ShopwareOrder[] $result */ + $result = $query->getQuery()->getResult(); + + $orderMap = []; + foreach ($result as $order) { + $orderMap[$order->getNumber()] = $order; + } + + return $orderMap; + } + + /** + * @param string $temporaryId + * + * @return ShopwareOrder | null + */ + public function getOrderByTemporaryId(string $temporaryId): ?ShopwareOrder + { + $query = $this->shopwareRepository + ->createQueryBuilder('orders') + ->andWhere('orders.temporaryId = :temporaryId') + ->setParameter(':temporaryId', $temporaryId) + ->orderBy('orders.id', 'DESC'); + + $result = $query->getQuery()->getResult(); + + return !empty($result) ? $result[0] : null; + } + + /** + * @param ShopwareOrder $order + * + * @return void + * + * @throws OptimisticLockException + */ + public function updateOrder(ShopwareOrder $order) + { + $manager = Shopware()->Models(); + $manager->persist($order); + $manager->flush(); + } + + /** + * @param int $id + * + * @return ShopwareOrder|null + */ + public function getOrderById(int $id): ?ShopwareOrder + { + $query = $this->shopwareRepository->createQueryBuilder('orders'); + $query->andWhere('orders.id = :id')->setParameter(':id', $id); + + $result = $query->getQuery()->getResult(); + + return !empty($result) ? $result[0] : null; + } +} diff --git a/Repositories/Wrapper/PaymentMeanRepository.php b/Repositories/Wrapper/PaymentMeanRepository.php new file mode 100644 index 00000000..c9349538 --- /dev/null +++ b/Repositories/Wrapper/PaymentMeanRepository.php @@ -0,0 +1,37 @@ +shopwareRepository = $repository; + } + + /** + * @return array + */ + public function getAdyenPaymentMeans(): array + { + $query = $this->shopwareRepository->createQueryBuilder('payment'); + $query->where('payment.name LIKE :paymentName')->setParameter(':paymentName', 'adyen_%'); + + return $query->getQuery()->getArrayResult(); + } +} diff --git a/Repositories/Wrapper/StoreRepository.php b/Repositories/Wrapper/StoreRepository.php new file mode 100644 index 00000000..daa3271e --- /dev/null +++ b/Repositories/Wrapper/StoreRepository.php @@ -0,0 +1,104 @@ +shopwareRepository = $repository; + } + + /** + * Returns array of sub shops in shop system. + * + * @return Shop[] + */ + public function getShopwareSubShops(): array + { + $query = $this->shopwareRepository->createQueryBuilder('shop'); + $query->where('shop.main IS NULL'); + + return $query->getQuery()->getResult(); + } + + /** + * Returns array of all language shops that belong to the provided sub shop ids + * + * @param int[] $subShopIds + * @return Shop[] + */ + public function getShopwareLanguageShops(array $subShopIds): array + { + $query = $this->shopwareRepository + ->createQueryBuilder('shop') + ->where('shop.main IN(:shopIds)') + ->setParameter('shopIds', $subShopIds, Connection::PARAM_INT_ARRAY); + + return $query->getQuery()->getResult(); + } + + /** + * Returns default store from system. + * + * @return Shop|null + */ + public function getShopwareDefaultShop(): ?Shop + { + $query = $this->shopwareRepository->createQueryBuilder('shop'); + $query->where('shop.default = 1'); + $result = $query->getQuery()->getResult(); + + return !empty($result) ? $result[0] : null; + } + + /** + * @param string $id + * + * @return Shop|null + */ + public function getStoreById(string $id): ?Shop + { + $query = $this->shopwareRepository->createQueryBuilder('shop'); + $query->where('shop.id = :storeId')->setParameter(':storeId', $id); + + $result = $query->getQuery()->getResult(); + + return !empty($result) ? $result[0] : null; + } + + /** + * Retrieves shop theme name. + * + * @return array + */ + public function getShopTheme(): array + { + $query = $this->shopwareRepository->createQueryBuilder('shop'); + $query->select(['template.template']) + ->innerJoin('shop.template', 'template') + ->where('shop.active = 1') + ->andWhere('shop.default = 1'); + + $result = $query->getQuery()->getArrayResult(); + + return !empty($result[0]) ? $result[0] : []; + } +} diff --git a/Repository/RecurringPayment/RecurringPaymentTokenRepository.php b/Repository/RecurringPayment/RecurringPaymentTokenRepository.php deleted file mode 100755 index 2dd07ae4..00000000 --- a/Repository/RecurringPayment/RecurringPaymentTokenRepository.php +++ /dev/null @@ -1,60 +0,0 @@ -entityManager = $entityManager; - $this->recurringPaymentTokenEntityRepository = $recurringPaymentTokenEntityRepository; - } - - public function fetchByCustomerIdAndOrderNumber(string $customerId, string $orderNumber): RecurringPaymentToken - { - $recurringPaymentToken = $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'customerId' => $customerId, - 'orderNumber' => $orderNumber, - ]); - - if (!($recurringPaymentToken instanceof RecurringPaymentToken)) { - throw RecurringPaymentTokenNotFoundException::withCustomerIdAndOrderNumber($customerId, $orderNumber); - } - - return $recurringPaymentToken; - } - - public function fetchPendingByPspReference(string $pspReference): RecurringPaymentToken - { - $recurringPaymentToken = $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'resultCode' => PaymentResultCode::pending()->resultCode(), - 'pspReference' => $pspReference, - ]); - - if (!($recurringPaymentToken instanceof RecurringPaymentToken)) { - throw RecurringPaymentTokenNotFoundException::withPendingResultCodeAndPspReference($pspReference); - } - - return $recurringPaymentToken; - } - - public function update(RecurringPaymentToken $recurringPaymentToken): void - { - $this->entityManager->persist($recurringPaymentToken); - $this->entityManager->flush($recurringPaymentToken); - } -} diff --git a/Repository/RecurringPayment/RecurringPaymentTokenRepositoryInterface.php b/Repository/RecurringPayment/RecurringPaymentTokenRepositoryInterface.php deleted file mode 100644 index 0eb07313..00000000 --- a/Repository/RecurringPayment/RecurringPaymentTokenRepositoryInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -recurringPaymentTokenRepository = $recurringPaymentTokenRepository; - $this->logger = $logger; - } - - public function fetchByCustomerIdAndOrderNumber(string $customerId, string $orderNumber): RecurringPaymentToken - { - try { - return $this->recurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber($customerId, $orderNumber); - } catch (RecurringPaymentTokenNotFoundException $exception) { - $this->logger->info($exception->getMessage(), ['exception' => $exception]); - - throw $exception; - } - } - - public function fetchPendingByPspReference(string $pspReference): RecurringPaymentToken - { - try { - return $this->recurringPaymentTokenRepository->fetchPendingByPspReference($pspReference); - } catch (RecurringPaymentTokenNotFoundException $exception) { - $this->logger->info($exception->getMessage(), ['exception' => $exception]); - - throw $exception; - } - } - - public function update(RecurringPaymentToken $recurringPaymentToken): void - { - try { - $this->recurringPaymentTokenRepository->update($recurringPaymentToken); - } catch (ORMException|ORMInvalidArgumentException $exception) { - $this->logger->error($exception->getMessage(), ['exception' => $exception]); - - throw RecurringPaymentTokenNotSavedException::withId($recurringPaymentToken->tokenIdentifier()); - } - } -} diff --git a/Resources/backend/performance/view/applepaymerchantassociation.js b/Resources/backend/performance/view/applepaymerchantassociation.js deleted file mode 100644 index f23b7414..00000000 --- a/Resources/backend/performance/view/applepaymerchantassociation.js +++ /dev/null @@ -1,20 +0,0 @@ - -//{block name="backend/performance/view/main/multi_request_tasks" append} -Ext.define('Shopware.apps.Performance.view.main.RegisterApplePayMerchantAssociation', { - override: 'Shopware.apps.Performance.view.main.MultiRequestTasks', - - initComponent: function() { - this.addProgressBar( - { - initialText: 'Register ApplePay merchant association URLs', - progressText: '[0] of [1] ApplePay merchant association URLs registered', - requestUrl: '{url controller=registerapplepayassociationurl action=register}' - }, - 'registerapplepayassociationurl', - 'seo' - ); - - this.callParent(arguments); - } -}); -//{/block} diff --git a/Resources/config.xml b/Resources/config.xml deleted file mode 100755 index f090a9b0..00000000 --- a/Resources/config.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - environment - - - - - - - - merchant_account - - - - api_key_test - - - - api_key_live - - - - client_key_test - - - - client_key_live - - - - api_url_prefix - - - - notification_hmac_test - - - - notification_auth_username_test - - - - notification_auth_password_test - - - - notification_hmac_live - - - - notification_auth_username_live - - - - notification_auth_password_live - - - - google_merchant_id - - - - paymentmethods_cache - - Caches the payment methods active in Adyen Customer Area for better performance. - - - manual_review_rejected_action - - Select which action to perform on the order if a risk rule sends a payment to case management and is rejected. See https://docs.adyen.com/risk-management/case-management for more information. - - - - - - - testAPIconnection - - - - - - - - - importPaymentMethod - - - - - - - - - InstallApplePayMerchantAssociation - - - - - - - - - diff --git a/Resources/cronjob.xml b/Resources/cronjob.xml deleted file mode 100644 index 2b9f230a..00000000 --- a/Resources/cronjob.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - Adyen: Process Notifications - Shopware_CronJob_AdyenPaymentProcessNotifications - true - 60 - false - - - Adyen: Import Payment Methods - AdyenPayment_CronJob_ImportPaymentMethods - true - 86400 - false - - diff --git a/Resources/frontend/js/adyen-checkout-controller.js b/Resources/frontend/js/adyen-checkout-controller.js new file mode 100644 index 00000000..e5749240 --- /dev/null +++ b/Resources/frontend/js/adyen-checkout-controller.js @@ -0,0 +1,336 @@ +;var AdyenComponents = window.AdyenComponents || {}; +(function () { + 'use strict'; + + function CheckoutConfigProvider() { + let configCache = {}; + + this.getConfiguration = async (configUrl) => { + if (configCache[configUrl]) { + return configCache[configUrl]; + } + + configCache[configUrl] = new Promise(async (resolve, reject) => { + let checkoutConfig = await (await fetch(configUrl, { + method: "GET" + })).json(); + + if (checkoutConfig.errorCode) { + reject('Checkout configuration error'); + } + + return resolve(checkoutConfig); + }); + + return configCache[configUrl]; + }; + } + + AdyenComponents.CheckoutConfigProvider = new CheckoutConfigProvider(); +})(); + +(function () { + 'use strict'; + // Use for local testing only, Amazon pay requires globally accessible URL + const devOnlyConfig = { + localShopDomain: '', + globalReplacementDomain: '' + }; + + const wallets = ['applepay', 'amazonpay', 'paywithgoogle', 'googlepay', 'paypal'], + giftCards = [ + 'auriga', 'babygiftcard', 'bloemengiftcard', 'cashcomgiftcard', 'eagleeye_voucher', 'entercard', + 'expertgiftcard', 'fashioncheque', 'fijncadeau', 'valuelink', 'fleuropbloemenbon', 'fonqgiftcard', + 'gallgall', 'givex', 'hallmarkcard', 'igive', 'ikano', 'kadowereld', 'kidscadeau', 'kindpas', + 'leisurecard', 'nationalebioscoopbon', 'netscard', 'oberthur', 'pathegiftcard', 'payex', 'podiumcard', + 'resursgiftcard', 'rotterdampas', 'genericgiftcard', 'schoolspullenpas', 'sparnord', 'sparebank', + 'svs', 'universalgiftcard', 'vvvcadeaubon', 'vvvgiftcard', 'webshopgiftcard', 'winkelcheque', + 'winterkledingpas', 'xponcard', 'yourgift', 'prosodie_illicado' + ]; + + /** + * Handles Adyen web components mounting and session data managing. + * + * @constructor + * + * @param {{ + * checkoutConfigUrl: string, + * showPayButton: boolean, + * sessionStorage: sessionStorage, + * onStateChange: function|undefined, + * onAdditionalDetails: function|undefined, + * onPayButtonClick: function|undefined, + * }} config + */ + function CheckoutController(config) { + const url = new URL(location.href); + if (url.hostname === devOnlyConfig.localShopDomain && devOnlyConfig.globalReplacementDomain) { + url.hostname = devOnlyConfig.globalReplacementDomain; + url.protocol = 'https:'; + } + + config.onStateChange = config.onStateChange || function () {}; + config.onAdditionalDetails = config.onAdditionalDetails || function () {}; + config.onPayButtonClick = config.onPayButtonClick || function (resolve, reject) { resolve(); }; + + const handleOnClick = (resolve, reject) => { + return config.onPayButtonClick(resolve, reject); + }; + + let checkout, + activeComponent, + isStateValid = true, + sessionStorage = config.sessionStorage || window.sessionStorage, + amazonCheckoutSessionId = url.searchParams.get('amazonCheckoutSessionId'), + paymentMethodSpecificConfig = { + "amazonpay": { + "productType": 'PayOnly', + "checkoutMode": 'ProcessOrder', + "chargePermissionType": 'OneTime', + "onClick": handleOnClick, + "returnUrl": url.href, + "cancelUrl": url.href + }, + "paywithgoogle": { "onClick": handleOnClick, "buttonSizeMode": "fill" }, + "googlepay": { "onClick": handleOnClick, "buttonSizeMode": "fill" }, + "paypal": { + "blockPayPalCreditButton": true, + "blockPayPalPayLaterButton": true, + "onClick": (source, event, self) => { + return handleOnClick(event.resolve, event.reject); + } + } + }; + + if (config.amount) { + paymentMethodSpecificConfig['amazonpay']['amount'] = config.amount; + paymentMethodSpecificConfig['amazonpay']['currency'] = config.amount.currency; + + paymentMethodSpecificConfig['paypal']['amount'] = config.amount; + } + + if (amazonCheckoutSessionId) { + paymentMethodSpecificConfig['amazonpay']['amazonCheckoutSessionId'] = amazonCheckoutSessionId; + paymentMethodSpecificConfig['amazonpay']['showOrderButton'] = false; + } + + /** + * + * @returns {Promise} + */ + const getCheckoutInstance = async () => { + if (!checkout) { + let checkoutConfig = await AdyenComponents.CheckoutConfigProvider.getConfiguration(config.checkoutConfigUrl); + + checkoutConfig.onChange = handleOnChange; + checkoutConfig.onSubmit = handleOnChange; + checkoutConfig.onAdditionalDetails = handleAdditionalDetails; + if (config.showPayButton) { + checkoutConfig.showPayButton = true; + } + + checkout = await AdyenCheckout(checkoutConfig); + } + + return Promise.resolve(checkout); + }; + + const handleOnChange = (state) => { + isStateValid = state.isValid; + if (isStateValid) { + sessionStorage.setItem('adyen-payment-method-state-data', JSON.stringify(state.data)); + } + + config.onStateChange(); + }; + + const handleAdditionalDetails = (state) => { + config.onAdditionalDetails(state.data); + }; + + /** + * Mounts adyen web component for a given type under the mount element. + * + * @param paymentType: string Web component payment method type + * @param mountElement: string|HTMLElement Dom elemnt or selector for dom element + * @param storedPaymentMethodId: string Optional stored payment method id to render component for + */ + const mount = (paymentType, mountElement, storedPaymentMethodId) => { + isStateValid = true; + + getCheckoutInstance().then((checkoutInstance) => { + unmount(); + + // Do not mount unavailable payment method + if (!findPaymentMethodConfig(checkoutInstance, paymentType)) { + return; + } + + sessionStorage.setItem('adyen-needs-sate-data-reinit', 'false'); + + if (!config.showPayButton && wallets.includes(paymentType)) { + return; + } + + let paymentMethodConfig = findSpecificPaymentMethodConfig(paymentType) || + findStoredPaymentMethodConfig(checkoutInstance, storedPaymentMethodId); + + // Configuration on the checkout instance level does not work for amazonpay, copy it on component level + if ('amazonpay' === paymentType && checkoutInstance.options.paymentMethodsConfiguration[paymentType]) { + paymentMethodConfig['configuration'] = checkoutInstance.options.paymentMethodsConfiguration[paymentType].configuration; + } + + activeComponent = checkoutInstance.create( + giftCards.includes(paymentType) ? 'giftcard' : paymentType, + paymentMethodConfig + ).mount(mountElement); + + isStateValid = !!activeComponent.isValid && activeComponent.isValid; + + config.onStateChange(); + + if (amazonCheckoutSessionId) { + activeComponent.submit(); + } + }); + }; + + const handleAdditionalAction = (action, mountElement) => { + getCheckoutInstance().then((checkoutInstance) => { + unmount(); + + checkoutInstance.createFromAction(action).mount(mountElement); + }); + }; + + const handleAction = (action) => { + activeComponent.handleAction(action); + } + + /** + * Unmounts the active web component (f there is one) and resets the payment method state + */ + const unmount = () => { + isStateValid = true; + sessionStorage.removeItem('adyen-payment-method-state-data'); + forceFetchingComponentStateData(); + + + if (activeComponent && checkout) { + checkout.remove(activeComponent); + activeComponent = null; + } + + config.onStateChange(); + }; + + /** + * Checks if payment method state is valid for currently mounted component + * + * @returns {boolean} + */ + const isPaymentMethodStateValid = () => { + return isStateValid; + }; + + /** + * Returns stringify version of payment method state data + * + * @returns {string} + */ + const getPaymentMethodStateData = () => { + return sessionStorage.getItem('adyen-payment-method-state-data'); + }; + + /** + * Returns true if Adyen web component was never mounted and therefore the initial payment state data collection + * is required. + * + * @returns {boolean} + */ + const isPaymentMethodStateReinitializationRequired = () => { + return 'true' === sessionStorage.getItem('adyen-needs-sate-data-reinit'); + }; + + /** + * Forces the validation errors to appear in currently mounted component + */ + const showValidation = () => { + if (activeComponent && 'showValidation' in activeComponent) { + activeComponent.showValidation(); + } + }; + + const forceFetchingComponentStateData = () => { + sessionStorage.setItem('adyen-needs-sate-data-reinit', 'true'); + } + + const findSpecificPaymentMethodConfig = (paymentType) => { + if (giftCards.includes(paymentType)) { + return { + type: 'giftcard', + brand: paymentType + }; + } + + return paymentMethodSpecificConfig[paymentType] || null; + }; + + const findStoredPaymentMethodConfig = (checkoutInstance, storedPaymentMethodId) => { + if (!storedPaymentMethodId) { + return null; + } + + for (const paymentMethod of checkoutInstance.options.paymentMethodsResponse.storedPaymentMethods) { + if (paymentMethod.id === storedPaymentMethodId) { + return { + ...paymentMethod, + storedPaymentMethodId + }; + } + } + + return null; + }; + + const findPaymentMethodConfig = (checkoutInstance, paymentMethodType) => { + if (!paymentMethodType) { + return null; + } + + let isGiftCard = giftCards.includes(paymentMethodType); + + for (const paymentMethod of checkoutInstance.options.paymentMethodsResponse.paymentMethods) { + if (paymentMethod.type === paymentMethodType) { + return paymentMethod; + } + + if (isGiftCard && paymentMethod.brand === paymentMethodType) { + return paymentMethod; + } + + if (paymentMethodType === 'googlepay' && paymentMethod.type === 'paywithgoogle') { + return paymentMethod; + } + + if (paymentMethodType === 'paywithgoogle' && paymentMethod.type === 'googlepay') { + return paymentMethod; + } + } + + return null; + }; + + this.mount = mount; + this.handleAdditionalAction = handleAdditionalAction; + this.handleAction = handleAction; + this.unmount = unmount; + this.getPaymentMethodStateData = getPaymentMethodStateData; + this.isPaymentMethodStateReinitializationRequired = isPaymentMethodStateReinitializationRequired; + this.isPaymentMethodStateValid = isPaymentMethodStateValid; + this.showValidation = showValidation; + this.forceFetchingComponentStateData = forceFetchingComponentStateData; + } + + AdyenComponents.CheckoutController = CheckoutController; +})(); diff --git a/Resources/frontend/js/adyen-donations-controller.js b/Resources/frontend/js/adyen-donations-controller.js new file mode 100644 index 00000000..dc44e079 --- /dev/null +++ b/Resources/frontend/js/adyen-donations-controller.js @@ -0,0 +1,86 @@ +;var AdyenComponents = window.AdyenComponents || {}; + +(function () { + 'use strict'; + + /** + * @constructor + * + * @param {{ + * donationsConfigUrl : string, + * makeDonation: function + * }} config + */ + function AdyenDonationsController(config) { + let donations, + activeComponent, + isStateValid = true; + + const getDonationsInstance = async () => { + if (!donations) { + let donationsConfig = await (await fetch(config.donationsConfigUrl, { + method: "GET" + })).json().catch((error) => { + return null + }); + + if (donationsConfig.length === 0) { + return null; + } + + if (donationsConfig.errorCode) { + throw 'Donations configuration error'; + } + + donations = await AdyenCheckout(donationsConfig); + } + + return Promise.resolve(donations); + } + + const handleOnDonate = (state, component) => { + isStateValid = state.isValid; + if (isStateValid) { + config.makeDonation(state.data); + } + }; + + const handleOnCancel = (state, component) => { + unmount(); + } + + const mount = (mountingElement) => { + let me = this, + donationInstance = getDonationsInstance(); + isStateValid = true; + + donationInstance.then((donationInstance) => { + if (!donationInstance) { + return; + } + + unmount(); + + activeComponent = donationInstance.create('donation', { + 'onDonate': handleOnDonate, + 'onCancel': handleOnCancel + }) + .mount(mountingElement); + }) + } + + const unmount = () => { + isStateValid = true; + + if (activeComponent && donations) { + donations.remove(activeComponent); + activeComponent = null; + } + } + + this.mount = mount; + this.unmount = unmount; + } + + AdyenComponents.DonationsController = AdyenDonationsController; +})(); diff --git a/Resources/frontend/js/jquery.adyen-checkout-error.js b/Resources/frontend/js/jquery.adyen-checkout-error.js deleted file mode 100644 index 286f98f5..00000000 --- a/Resources/frontend/js/jquery.adyen-checkout-error.js +++ /dev/null @@ -1,114 +0,0 @@ -;(function ($) { - 'use strict'; - - /** - * Plugin to show errors in the Shopware Checkout using javascript 'simple pub/sub' events. - * Initialise using the StateManager - * - * -- Adding an error message - * $.publish('plugin/AdyenPaymentCheckoutError/addError', 'Something went wrong'); - * - * -- Clearing all Adyen error messages - * $.publish('plugin/AdyenPaymentCheckoutError/cleanErrors'); - */ - $.plugin('adyen-checkout-error', { - defaults: { - /** - * @var string errorClass - * CSS classes for the error element - */ - errorClass: 'alert is--error is--rounded is--adyen-error', - - /** - * @var string errorMessageClass - * CSS classes for the error message element - */ - errorMessageClass: 'alert--content', - - /** - * @var bool showIcon - * Whether to show or not show the icon - */ - showIcon: true, - - /** - * @var string errorMessageClass - * The icon to show. Defaults to a cross - */ - showIconIcon: 'icon--cross' - }, - - init: function () { - var me = this; - - me.applyDataAttributes(); - me.eventListeners(); - }, - - /** - * Initialise event listeners for error handling - */ - eventListeners: function () { - var me = this; - $.subscribe(me.getEventName('plugin/AdyenPaymentCheckoutError/addError'), $.proxy(me.onAddError, me)); - $.subscribe(me.getEventName('plugin/AdyenPaymentCheckoutError/cleanErrors'), $.proxy(me.onCleanErrors, me)); - $.subscribe(me.getEventName('plugin/AdyenPaymentCheckoutError/scrollToErrors'), $.proxy(me.onScrollTo, me)); - }, - - /** - * Add errors to the element - * - * @param o To be ignored - * @param message The error message - */ - onAddError: function (o, message) { - var me = this; - me.$el.append(me.createError(message)); - }, - - /** - * Removes all errors from the element - */ - onCleanErrors: function () { - var me = this; - me.$el.children().remove(); - }, - - onScrollTo: function () { - var me = this; - window.scroll(0, me.$el.offset().top - (window.innerHeight/2)); - }, - - /** - * Create a Error message jQuery element - * - * @param message - * @returns {jQuery} - */ - createError: function (message) { - var me = this; - - var error = $('
') - .addClass(me.opts.errorClass); - error.append( - $('
') - .addClass(me.opts.errorMessageClass) - .html(message) - ); - - if (me.opts.showIcon) { - var icon = $('
') - .addClass('alert--icon') - .append( - $('') - .addClass('icon--element') - .addClass(me.opts.showIconIcon) - ); - - error.prepend(icon); - } - - return error; - }, - }); -})(jQuery); \ No newline at end of file diff --git a/Resources/frontend/js/jquery.adyen-confirm-order.js b/Resources/frontend/js/jquery.adyen-confirm-order.js index 2d1c16ae..7407e8dc 100644 --- a/Resources/frontend/js/jquery.adyen-confirm-order.js +++ b/Resources/frontend/js/jquery.adyen-confirm-order.js @@ -6,100 +6,65 @@ * Plugin default options. */ defaults: { - /** - * Default shopLocale when no locate is assigned - * - * @type {string} - */ - shopLocale: 'en-US', - /** - * Fallback environment variable - * - * @type {string} - */ - adyenEnvironment: 'test', - adyenClientKey: '', - enrichedPaymentMethods: {}, + checkoutConfigUrl: '', + additionalDataUrl: '', + checkoutShippingPaymentUrl: '/checkout/shippingPayment/sTarget/checkout', + adyenPaymentMethodType: '', placeOrderSelector: '.table--actions button[type=submit]', confirmFormSelector: '#confirm--form', - adyenType: '', - adyenGoogleConfig: {}, - adyenApplePayConfig: {}, - adyenPaymentState: {}, - adyenIsAdyenPayment: false, - adyenConfigAjaxUrl: '/frontend/adyenconfig/index', - adyenAjaxDoPaymentUrl: '/frontend/adyen/ajaxDoPayment', - adyenAjaxPaymentDetails: '/frontend/adyen/paymentDetails', - checkoutShippingPaymentUrl: '/checkout/shippingPayment/sTarget/checkout', - accountLoginUrl: '/account/login/sTarget/checkout/sTargetAction/confirm/showNoAccount/true', - adyenSnippets: { - errorTransactionCancelled: 'Your transaction was cancelled by the Payment Service Provider.', - errorTransactionProcessing: 'An error occurred while processing your payment.', - errorTransactionRefused: 'Your transaction was refused by the Payment Service Provider.', - errorTransactionUnknown: 'Your transaction was cancelled due to an unknown reason.', - errorTransactionNoSession: 'Your transaction was cancelled due to an unknown reason. Please make sure your browser allows cookies.', - errorGooglePayNotAvailable: 'Google Pay is currently not available.', - errorApplePayNotAvailable: 'Apple Pay is currently not available.' - }, + stateDataInputSelector: 'input[name=adyenPaymentMethodStateData]' }, - paymentMethodSession: 'paymentMethod', - storePaymentMethodSession: 'storePaymentMethod', - adyenConfiguration: {}, - adyenCheckout: null, - init: function () { - var me = this; + submitButtonReplacingComponents: ['applepay', 'amazonpay', 'paywithgoogle', 'googlepay', 'paypal'], + checkoutController: null, - me.sessionStorage = StorageManager.getStorage('session'); + init: function () { + let me = this; me.applyDataAttributes(); - me.eventListeners(); - me.checkSetSession(); - me.setConfig(); - me.setCheckout(); - me.handleCheckoutButton(); - }, - eventListeners: function () { - var me = this; - me._on(me.opts.placeOrderSelector, 'click', $.proxy(me.onPlaceOrder, me)); - }, - checkSetSession: function () { - var me = this; - - if (!me.opts.adyenIsAdyenPayment) { - me.sessionStorage.removeItem(me.paymentMethodSession); + if (!me.submitButtonReplacingComponents.includes(me.opts.adyenPaymentMethodType)) { return; } - var parsedPaymentMethodSession = JSON.parse(me.getPaymentMethod() || '{}'); - if (!$.isEmptyObject(me.opts.adyenPaymentState) - && 0 === Object.keys(parsedPaymentMethodSession).length) { - me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(me.opts.adyenPaymentState)); - return; - } + me.checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "showPayButton": true, + "sessionStorage": StorageManager.getStorage('session'), + "onStateChange": $.proxy(me.submitOrder, me), + "onAdditionalDetails": $.proxy(me.onAdditionalDetails, me), + "onPayButtonClick": $.proxy(me.onPayButtonClick, me) + }); - if (!me.sessionStorage.getItem(me.paymentMethodSession)) { - window.location.href = me.opts.checkoutShippingPaymentUrl; - } + me.replacePlaceOrderButton(); }, - onPlaceOrder: function (event) { - var me = this; - if (typeof event !== 'undefined') { - event.preventDefault(); - } + replacePlaceOrderButton: function () { + let me = this, + orderButton = $(me.opts.placeOrderSelector); - me.clearAdyenError(); + orderButton.parent().append( + $('
') + .attr('data-adyen-submit-button', 'true') + .addClass('right') + ); + orderButton.remove(); - if (!me.sessionStorage.getItem(me.paymentMethodSession)) { - if (me.opts.adyenIsAdyenPayment) { - this.addAdyenError(me.opts.adyenSnippets.errorTransactionNoSession); + me.checkoutController.mount(me.opts.adyenPaymentMethodType, '[data-adyen-submit-button]'); + }, - return; - } + onPayButtonClick: function (resolve, reject) { + let isValid = $(this.opts.confirmFormSelector)[0].checkValidity(); - $(me.opts.confirmFormSelector).submit(); + isValid ? resolve() : reject('Form validation error.'); + + return isValid; + }, + + submitOrder: function () { + let me = this; + + if (!me.checkoutController.getPaymentMethodStateData()) { return; } @@ -107,268 +72,64 @@ return; } - $.loadingIndicator.open(); + // Make sure that wallet payment state data is submitted + $(me.opts.stateDataInputSelector).val(me.checkoutController.getPaymentMethodStateData()); + if (me.opts.adyenPaymentMethodType !== 'paypal') { + $(me.opts.confirmFormSelector).submit(); - var data = { - 'paymentMethod': me.getPaymentMethod(), - 'storePaymentMethod': me.getStorePaymentMethod(), - 'browserInfo': me.getBrowserInfo(), - 'origin': window.location.origin, - 'sComment': me.getComment() - }; + return; + } + var form = $(me.opts.confirmFormSelector); + var url = form.attr('action'); $.ajax({ - method: 'POST', - dataType: 'json', - url: me.opts.adyenAjaxDoPaymentUrl, - data: data, - success: function (response) { - if (response['status'] === 'success') { - me.handlePaymentData(response['content'], response['sUniqueID'], response['adyenTransactionId']); - } else { - me.addAdyenError(response['content']); + type: "POST", + url: url+'/isXHR/1', + data: form.serialize(), + success: function(data) { + if (data.nextStepUrl) { + window.location.href = data.nextStepUrl; + return; } - $.loadingIndicator.close(); - }, - error: me.handleAjaxRequestError.bind(me) - }); - }, - handlePaymentData: function (data, sUniqueID = null, adyenTransactionId = null) { - var me = this; - switch (data.resultCode) { - case 'Authorised': - me.handlePaymentDataAuthorised(data, sUniqueID); - break; - case 'IdentifyShopper': - case 'ChallengeShopper': - case 'Pending': - case 'RedirectShopper': - me.handlePaymentDataCreateFromAction(data, sUniqueID, adyenTransactionId); - break; - default: - me.handlePaymentDataError(data); - break; - } - }, - handlePaymentDataAuthorised: function (data, sUniqueID = null) { - var me = this; - var input = $("").attr("type", "hidden").attr("name", "sUniqueID").val(sUniqueID); - $(me.opts.confirmFormSelector).append(input).submit(); - }, - handlePaymentDataCreateFromAction: function (data, sUniqueID = null, adyenTransactionId = null) { - var me = this; - var payload = { - resultCode: data.resultCode, - type: data.action.type, - subtype: data.action.subtype - }; - var modal = $.modal.open('
', { - showCloseButton: false, - closeOnOverlay: false, - additionalClass: 'adyen-modal' - }); - - // data.action: "redirect" errors are handled by Process::returnAction() - me.adyenCheckout - .createFromAction(data.action, { - onAdditionalDetails: function (state) { - modal.close(); - $.ajax({ - method: 'POST', - dataType: 'json', - url: me.opts.adyenAjaxPaymentDetails, - data: { - 'action': payload, - 'details': state.data.details, - 'adyenTransactionId': adyenTransactionId - }, - success: function (response) { - me.handlePaymentData(response, sUniqueID, adyenTransactionId); - }, - error: me.handleAjaxRequestError.bind(me) - }); - }, - onError: function (error) { - console.error(error); + if (!data.action) { + window.location.href = me.opts.checkoutShippingPaymentUrl; + return; } - }) - .mount('#AdyenModal'); - }, - handleAjaxRequestError: function (xhr) { - if (xhr.status === 401) { - window.location.href = this.opts.accountLoginUrl; - } - - this.addAdyenError(this.opts.adyenSnippets.errorTransactionProcessing); - $.loadingIndicator.close(); - }, - handlePaymentDataError: function (data) { - var me = this; - - $.loadingIndicator.close(); - switch (data.resultCode) { - case 'Cancelled': - this.addAdyenError(me.opts.adyenSnippets.errorTransactionCancelled); - break; - case 'Error': - this.addAdyenError(me.opts.adyenSnippets.errorTransactionProcessing); - break; - case 'Refused': - this.addAdyenError(me.opts.adyenSnippets.errorTransactionRefused); - break; - default: - this.addAdyenError(me.opts.adyenSnippets.errorTransactionUnknown); - break; - } - }, - handleCheckoutButton: function () { - var me = this; + me.signature = data.signature; + me.reference = data.reference; + me.paymentData = null; + if (data.action.paymentData) { + me.paymentData = data.action.paymentData + } - var paymentMethodsConfig = { - 'paywithgoogle': { - config: me.opts.adyenGoogleConfig, - errorMessage: me.opts.adyenSnippets.errorGooglePayNotAvailable + me.checkoutController.handleAction(data.action); }, - 'applepay': { - config: me.opts.adyenApplePayConfig, - errorMessage: me.opts.adyenSnippets.errorApplePayNotAvailable + error: function(data) { + window.location.href = me.opts.checkoutShippingPaymentUrl; } - }; - - if (paymentMethodsConfig.hasOwnProperty(me.opts.adyenType)) { - var paymentMethodConfig = paymentMethodsConfig[me.opts.adyenType]; - me.replaceCheckoutButton(me.opts.adyenType, paymentMethodConfig.config, paymentMethodConfig.errorMessage); - } - }, - replaceCheckoutButton: function (paymentMethod, config, errorMessage) { - var me = this; - - if (0 === Object.keys(config).length) { - this.addAdyenError(errorMessage); - console.error('Adyen: Missing ' + paymentMethod + ' configuration'); - return; - } - - var paymentButtonContainer = paymentMethod + '-container'; - var orderButton = $(me.opts.placeOrderSelector); - orderButton.parent().append( - $('
') - .attr('id', paymentButtonContainer) - .addClass('right') - ); - orderButton.remove(); - - config.onSubmit = function (state, component) { - me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(state.data.paymentMethod)); - me.onPlaceOrder(); - }; - - var component = me.adyenCheckout.create(paymentMethod, config); - component - .isAvailable() - .then(function () { - component.mount('#' + paymentButtonContainer); - }) - .catch(function (e) { - me.addAdyenError(errorMessage); - }); - }, - addAdyenError: function (message) { - var me = this; - $.publish('plugin/AdyenPaymentCheckoutError/addError', message); - $.publish('plugin/AdyenPaymentCheckoutError/scrollToErrors'); - - $(me.opts.placeOrderSelector) - .removeAttr('disabled') - .removeClass('disabled') - .find('.js--loading') - .remove(); - }, - clearAdyenError: function () { - $.publish('plugin/AdyenPaymentCheckoutError/cleanErrors'); + }); }, - setConfig: function () { - var me = this; - var adyenConfigSession = JSON.parse(me.getAdyenConfigSession()); + onAdditionalDetails: function (additionalData) { + let me = this; + if (me.paymentData) { + additionalData.paymentData = me.paymentData + } $.ajax({ - method: 'GET', - async: false, + method: 'POST', dataType: 'json', - url: me.opts.adyenConfigAjaxUrl, + url: me.opts.additionalDataUrl + "/signature/" + me.signature + "/reference/" + me.reference + '/isXHR/1', + data: additionalData, success: function (response) { - if (response['status'] === 'success') { - me.opts.shopLocale = response['shopLocale']; - me.opts.adyenClientKey = response['clientKey']; - me.opts.adyenEnvironment = response['environment']; - me.opts.enrichedPaymentMethods = response['enrichedPaymentMethods']; - } else { - me.addAdyenError(response['content']); - } - - $.loadingIndicator.close(); + window.location.href = response.nextStepUrl; + }, + error: function () { + window.location.href = me.opts.checkoutShippingPaymentUrl; } }); - - var adyenPaymentMethodsResponseConfig = me.opts.enrichedPaymentMethods.reduce( - function (rawAdyen, enrichedPaymentMethod) { - var isAdyenPaymentMethod = enrichedPaymentMethod.isAdyenPaymentMethod || false; - if (true === isAdyenPaymentMethod) { - rawAdyen.push(enrichedPaymentMethod.metadata); - } - - return rawAdyen; - }, - [] - ); - - me.adyenConfiguration = { - locale: adyenConfigSession ? adyenConfigSession.locale : me.opts.shoplocale, - environment: adyenConfigSession ? adyenConfigSession.environment : me.opts.adyenenvironment, - clientKey: adyenConfigSession ? adyenConfigSession.clientKey : me.opts.adyenclientkey, - paymentMethodsResponse: Object.assign({}, adyenPaymentMethodsResponseConfig), - onAdditionalDetails: me.handleOnAdditionalDetails.bind(me) - }; - }, - setCheckout: function () { - var me = this; - - me.adyenCheckout = new AdyenCheckout(me.adyenConfiguration); - }, - getComment: function() { - return $('[data-storagekeyname="sComment"]').val(); - }, - getPaymentMethod: function () { - var me = this; - - return me.sessionStorage.getItem(me.paymentMethodSession); - }, - getStorePaymentMethod: function () { - var me = this; - - return me.sessionStorage.getItem(me.storePaymentMethodSession); - }, - getAdyenConfigSession: function () { - var me = this; - - return me.sessionStorage.getItem('adyenConfig'); - }, - getBrowserInfo: function () { - return { - 'language': navigator.language, - 'userAgent': navigator.userAgent, - 'colorDepth': window.screen.colorDepth, - 'screenHeight': window.screen.height, - 'screenWidth': window.screen.width, - 'timeZoneOffset': new Date().getTimezoneOffset(), - 'javaEnabled': navigator.javaEnabled() - }; - }, - handleOnAdditionalDetails: function (state, component) { - $.loadingIndicator.close(); } }); })(jQuery); diff --git a/Resources/frontend/js/jquery.adyen-cookie-consent-visibility-handler.js b/Resources/frontend/js/jquery.adyen-cookie-consent-visibility-handler.js new file mode 100644 index 00000000..e83b8017 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-cookie-consent-visibility-handler.js @@ -0,0 +1,44 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-cookie-consent-visibility-handler', { + /** + * Plugin default options. + */ + defaults: { + paymentMethodBlockSelector: '.payment--method.block', + adyenPaymentMethodSelector: '[data-adyen-payment-method]' + }, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + $.subscribe(me.getEventName('plugin/swShippingPayment/onInputChanged'), $.proxy(me.handleAdyenPaymentVisibility, me)); + $.subscribe('plugin/swCookiePermission/onDeclineButtonClick', $.proxy(me.handleAdyenPaymentVisibility, me)); + + me.handleAdyenPaymentVisibility(); + }, + + handleAdyenPaymentVisibility: function() { + let me = this; + + if (!window.StateManager.hasCookiesAllowed()) { + me.hideAllAdyenPaymentMethods(); + } + + me.$el.removeClass('adyen-hidden--all'); + }, + + hideAllAdyenPaymentMethods: function () { + let me = this; + + me.$el.find(me.opts.adyenPaymentMethodSelector) + .closest(me.opts.paymentMethodBlockSelector) + .addClass('adyen-hidden--all'); + } + }); + +})(jQuery); + diff --git a/Resources/frontend/js/jquery.adyen-donations.js b/Resources/frontend/js/jquery.adyen-donations.js new file mode 100644 index 00000000..ded62ffb --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-donations.js @@ -0,0 +1,46 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-donations', { + /** + * Plugin default options. + */ + defaults: { + donationsConfigUrl: '', + makeDonationsUrl: '' + }, + + donationsController : null, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + me.donationsController = new AdyenComponents.DonationsController({ + "donationsConfigUrl": me.opts.donationsConfigUrl, + "makeDonation": $.proxy(me.makeDonation, me) + }); + + me.donationsController.mount(me.$el[0]); + }, + + makeDonation: function (data) { + let me = this; + + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.makeDonationsUrl, + data: data, + success: function () { + window.location.reload(); + }, + error: function () { + me.donationsController.unmount(); + window.location.reload(); + } + }); + } + }); +})(jQuery); diff --git a/Resources/frontend/js/jquery.adyen-express-checkout.js b/Resources/frontend/js/jquery.adyen-express-checkout.js new file mode 100644 index 00000000..1ee896ea --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-express-checkout.js @@ -0,0 +1,111 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-express-checkout', { + /** + * Plugin default options. + */ + defaults: { + checkoutConfigUrl: '', + additionalDataUrl: '', + checkoutShippingPaymentUrl: '/checkout/shippingPayment/sTarget/checkout', + adyenPaymentMethodType: '', + stateDataInputSelector: 'input[name=adyenExpressPaymentMethodStateData]', + confirmFormSelector: 'form[data-adyen-express-checkout-form]', + }, + + checkoutController: null, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + me.checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "showPayButton": true, + "sessionStorage": StorageManager.getStorage('session'), + "onStateChange": $.proxy(me.submitOrder, me), + "onAdditionalDetails": $.proxy(me.onAdditionalDetails, me) + }); + + me.mountExpressCheckoutButtons(); + }, + + mountExpressCheckoutButtons: function () { + let me = this; + + me.checkoutController.mount(me.opts.adyenPaymentMethodType, me.$el[0]); + }, + + submitOrder: function () { + let me = this; + + if (!me.checkoutController.getPaymentMethodStateData()) { + me.checkoutController.forceFetchingComponentStateData(); + + return; + } + + let expressCheckoutForm = me.$el.closest(me.opts.confirmFormSelector); + + // Make sure that wallet payment state data is submitted + expressCheckoutForm.find(me.opts.stateDataInputSelector).val(me.checkoutController.getPaymentMethodStateData()); + if (me.opts.adyenPaymentMethodType !== 'paypal') { + expressCheckoutForm.submit(); + + return; + } + + var url = expressCheckoutForm.attr('action'); + $.ajax({ + type: "POST", + url: url+'/isXHR/1', + data: expressCheckoutForm.serialize(), + success: function(data) { + if (data.nextStepUrl) { + window.location.href = data.nextStepUrl; + return; + } + + if (!data.action) { + window.location.href = me.opts.checkoutShippingPaymentUrl; + return; + } + + me.signature = data.signature; + me.reference = data.reference; + me.paymentData = null; + if (data.action.paymentData) { + me.paymentData = data.action.paymentData + } + + me.checkoutController.handleAction(data.action); + }, + error: function(data) { + window.location.href = me.opts.checkoutShippingPaymentUrl; + } + }); + }, + + onAdditionalDetails: function (additionalData) { + let me = this; + + if (me.paymentData) { + additionalData.paymentData = me.paymentData + } + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.additionalDataUrl + "/signature/" + me.signature + "/reference/" + me.reference + '/isXHR/1', + data: additionalData, + success: function (response) { + window.location.href = response.nextStepUrl; + }, + error: function () { + window.location.href = me.opts.checkoutShippingPaymentUrl; + } + }); + } + }); +})(jQuery); diff --git a/Resources/frontend/js/jquery.adyen-finish-order.js b/Resources/frontend/js/jquery.adyen-finish-order.js deleted file mode 100644 index 7610fb19..00000000 --- a/Resources/frontend/js/jquery.adyen-finish-order.js +++ /dev/null @@ -1,23 +0,0 @@ -;(function ($) { - 'use strict'; - - $.plugin('adyen-finish-order', { - sessions: [ - 'adyenConfig', - 'paymentMethod' - ], - - init: function () { - var me = this; - me.sessionStorage = StorageManager.getStorage('session'); - me.cleanupSessions(); - }, - - cleanupSessions: function () { - var me = this; - me.sessions.forEach(function (session) { - me.sessionStorage.removeItem(session); - }); - } - }); -})(jQuery); diff --git a/Resources/frontend/js/jquery.adyen-payment-additional-action.js b/Resources/frontend/js/jquery.adyen-payment-additional-action.js new file mode 100644 index 00000000..eef598c2 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-payment-additional-action.js @@ -0,0 +1,57 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-payment-additional-action', { + /** + * Plugin default options. + */ + defaults: { + checkoutConfigUrl: '', + additionalDataUrl: '', + additionalActionSelector: '#adyen-additional-action', + checkoutShippingPaymentUrl: '/checkout/shippingPayment/sTarget/checkout' + }, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + if ($(me.opts.additionalActionSelector).html() === '' || + $(me.opts.additionalActionSelector).html() === 'undefined') { + return; + } + + const additionalAction = JSON.parse($(me.opts.additionalActionSelector).html()); + if (!additionalAction || !additionalAction.type) { + window.location.href = me.opts.checkoutShippingPaymentUrl; + } + + let checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "onAdditionalDetails": $.proxy(me.onAdditionalDetails, me), + "sessionStorage": StorageManager.getStorage('session') + }); + + checkoutController.handleAdditionalAction(additionalAction, me.$el[0]); + }, + + onAdditionalDetails: function (additionalData) { + let me = this; + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.additionalDataUrl + '/isXHR/1', + data: additionalData, + success: function (response) { + window.location.href = response.nextStepUrl; + }, + error: function () { + window.location.href = me.opts.checkoutShippingPaymentUrl; + } + }); + } + }); + +})(jQuery); + diff --git a/Resources/frontend/js/jquery.adyen-payment-method-state-data-setter.js b/Resources/frontend/js/jquery.adyen-payment-method-state-data-setter.js new file mode 100644 index 00000000..38d01252 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-payment-method-state-data-setter.js @@ -0,0 +1,34 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-payment-method-state-data-setter', { + /** + * Plugin default options. + */ + defaults: { + checkoutConfigUrl: '', + checkoutShippingPaymentUrl: '/checkout/shippingPayment/sTarget/checkout' + }, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + let checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "sessionStorage": StorageManager.getStorage('session') + }); + + if (checkoutController.isPaymentMethodStateReinitializationRequired()) { + window.location.href = me.opts.checkoutShippingPaymentUrl; + } + + if (checkoutController.getPaymentMethodStateData()) { + me.$el.val(checkoutController.getPaymentMethodStateData()); + } + } + }); + +})(jQuery); + diff --git a/Resources/frontend/js/jquery.adyen-payment-selection.js b/Resources/frontend/js/jquery.adyen-payment-selection.js index b61841de..c85f258b 100644 --- a/Resources/frontend/js/jquery.adyen-payment-selection.js +++ b/Resources/frontend/js/jquery.adyen-payment-selection.js @@ -1,665 +1,177 @@ ;(function ($) { 'use strict'; + $.plugin('adyen-payment-selection', { /** * Plugin default options. */ defaults: { - adyenClientKey: '', - enrichedPaymentMethods: {}, - adyenOrderTotal: '', - adyenOrderCurrency: '', - resetSessionUrl: '', - adyenConfigAjaxUrl: '', - /** - * Fallback environment variable - * - * @type {string} - */ - adyenEnvironment: 'test', - /** - * Default shopLocale when no locate is assigned - * - * @type {string} - */ - shopLocale: 'en-US', - /** - * Selector for the shipping payment content. - * - * @type {String} - */ - shippingPaymentContentSelector: '#shipping_payment_wrapper', - /** - * Selector for stored payment method content. - * - * @type {String} - */ - storedPaymentContentSelector: '#stored_payment_wrapper', - /** - * Selector for the payment form. - * - * @type {String} - */ + checkoutConfigUrl: '', formSelector: '#shippingPaymentForm', - /** - * Selector for the payment method select fields. - * - * @type {String} - */ - paymentMethodSelector: '.payment--method', - /** - * Selector for the payment method component wrapper. - * - * @type {String} - */ - methodBankdataSelector: '.method--bankdata', - /** - * Classname for 'Update Payment informations' button - */ - classChangePaymentInfo: 'method--change-info', - /** - * Selector for the payment method form submit button element. - * - * @type {String} - */ - paymentMethodFormSubmitSelector: '#shippingPaymentForm button[type=submit], button[form="shippingPaymentForm"]', - /** - * @type {string} the group name of Gift card types - */ - giftCardGroupName: 'Gift Card', - /** - * @type {string} Adyen payment method type for ApplePay - */ - applePayType: 'applepay', - /** - * @type {string} Adyen payment method type for GooglePay - */ - googlePayType: 'paywithgoogle', - /** - * @type {string} Adyen payment method type for ApplePay - */ - onlineBankingPLType: 'onlineBanking_PL', - /** - * @type {string} Snippets associated with the payment page - */ - adyenSnippets: { - updatePaymentInformation: 'Update your payment information' - }, + paymentMeanSelector: 'input[type=radio][name=payment]', paymentMeanChangerSelector: 'input[type=radio][name=payment], label[for^=payment_mean]', shippingChangerSelector: 'input[type=radio][name=sDispatch], label[for^=confirm_dispatch]', storedPaymentMethodSelector: 'input[type=hidden][name=adyenStoredPaymentMethodId]', + formSubmitButtonSelector: '#shippingPaymentForm button[type=submit], button[form="shippingPaymentForm"]', activePaymentMeanSelector: 'input[type=radio][name=payment][checked]', + paymentMethodBlockSelector: '.payment--method.block', + paymentMethodComponentContainerSelector: '.method--bankdata', + updatePaymentInfoButtonClass: 'method--change-info', + updatePaymentInfoButtonText: 'Update your payment information' }, - selectedPaymentElementId: '', - selectedPaymentId: '', - adyenConfiguration: {}, - adyenCheckout: null, - changeInfosButton: null, - paymentMethodSession: 'paymentMethod', - storePaymentMethodSession: 'storePaymentMethod', - adyenConfigSession: 'adyenConfig', + checkoutController: null, + selectedPaymentMeanId: null, init: function () { - var me = this; - me.sessionStorage = StorageManager.getStorage('session'); + let me = this; me.applyDataAttributes(); - me.eventListeners(); - me.setConfig(); - me.handleVisibility(); - me.setCheckout(); - me.handleSelectedMethod(); - }, - eventListeners: function () { - var me = this; + + me.checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "sessionStorage": StorageManager.getStorage('session'), + "onStateChange": $.proxy(me.updateFormSubmitButton, me) + }); $(document).on('submit', me.opts.formSelector, $.proxy(me.onPaymentFormSubmit, me)); $(document).on('mousedown', me.opts.paymentMeanChangerSelector, $.proxy(me.onPaymentMethodBeforeChange, me)); $(document).on('mousedown', me.opts.shippingChangerSelector, $.proxy(me.onShippingBeforeChange, me)); $.subscribe(me.getEventName('plugin/swShippingPayment/onInputChanged'), $.proxy(me.onPaymentChangedAfter, me)); - }, - handleVisibility: function () { - if (!window.StateManager.hasCookiesAllowed()) { - this.hideAllAdyenPaymentMethods(); - $(this.opts.shippingPaymentContentSelector).removeClass('adyen-hidden--all'); - return; - } - - this.handleApplePayVisibility(); - $(this.opts.shippingPaymentContentSelector).removeClass('adyen-hidden--all'); + me.onPaymentChangedAfter(); }, - hideAllAdyenPaymentMethods: function () { - this.hideStoredAdyenPaymentMethods(); - this.hideAdyenPaymentMethods(); - }, - hideStoredAdyenPaymentMethods: function () { - for (let i = 0; i < this.opts.enrichedPaymentMethods.length; i++) { - if (this.opts.enrichedPaymentMethods[i].isAdyenPaymentMethod && this.opts.enrichedPaymentMethods[i].isStoredPayment) { - $(this.opts.storedPaymentContentSelector).addClass('adyen-hidden--all'); - return; - } - } - }, - hideAdyenPaymentMethods: function () { - for (let i = 0; i < this.opts.enrichedPaymentMethods.length; i++) { - if (this.opts.enrichedPaymentMethods[i].isAdyenPaymentMethod) { - $('#payment_mean' + this.opts.enrichedPaymentMethods[i].id) - .parents(this.opts.paymentMethodSelector).addClass('adyen-hidden--all'); - } - } - }, - handleApplePayVisibility: function () { - var me = this; - var applePayAvailable = window.ApplePaySession || false; - if (applePayAvailable) { - return; - } - var applePayMethod = me.opts.enrichedPaymentMethods.filter(function (enrichedPaymentMethod) { - return enrichedPaymentMethod.adyenType === me.opts.applePayType; - })[0] || {}; - if (!applePayMethod) { - return; - } - - $('#payment_mean' + applePayMethod.id).parents(this.opts.paymentMethodSelector).addClass('adyen-hidden--all'); - }, - onPaymentFormSubmit: function (e) { - var me = this; - var $formSubmit = $(me.opts.paymentMethodFormSubmitSelector); - if ($formSubmit.hasClass('is--disabled')) { - e.preventDefault(); - return false; - } - var $paymentElement = $('#' + me.selectedPaymentElementId)[0]; - var paymentMethod = this.getPaymentMethodById($paymentElement.value); - if (paymentMethod.isStoredPayment) { - $formSubmit.append( - $('') - ); - } - $paymentElement.value = paymentMethod.id; - }, - onPaymentMethodBeforeChange: function (event) { - let me = this, - $paymentElement = $('#' + event.target.id)[0], - paymentMethod = this.getPaymentMethodById($paymentElement.value); - - me.updateStoredMethodId(paymentMethod); - }, - - onShippingBeforeChange: function (event) { - let me = this, - $paymentElement = $('#' + event.target.id)[0], - paymentMethod = this.getPaymentMethodById($paymentElement.value); - - me.updateStoredMethodId(paymentMethod); - }, - updateStoredMethodId: function (selectedPaymentMeanEl) { - let me = this, - $formSubmit = $(me.opts.paymentMethodFormSubmitSelector), - storedPaymentMethodEl = $(me.opts.storedPaymentMethodSelector); - - if (storedPaymentMethodEl.data("adyen-payment-method")) { - storedPaymentMethodEl.val(selectedPaymentMeanEl.data('adyen-stored_payment_method_id')); - } else { - $formSubmit.append( - $('') - ); - } - }, - isPaymentElement: function (elementId) { - return $('#' + elementId).parents(this.opts.paymentMethodSelector).length > 0; - }, onPaymentChangedAfter: function () { - var me = this, + let me = this, selectedPaymentMeanEl = $(me.opts.activePaymentMeanSelector).first(); - var previousSelectedPaymentElementId = me.selectedPaymentElementId; - var selectedPaymentElementId = selectedPaymentMeanEl.attr('id'); + if ( + selectedPaymentMeanEl.attr("id") === me.selectedPaymentMeanId && + me.checkoutController.getPaymentMethodStateData() + ) { + me.showUpdatePaymentInfoButton(); - // only update when switching payment-methods (not on shipping methods) - if (!me.isPaymentElement(selectedPaymentElementId)) { return; } - me.selectedPaymentElementId = selectedPaymentElementId; - - var elementValue = selectedPaymentMeanEl.val(); - var paymentMethod = this.getPaymentMethodById(elementValue); - me.selectedPaymentId = paymentMethod.isStoredPayment ? paymentMethod.stored_method_id : elementValue; - - if (previousSelectedPaymentElementId !== me.selectedPaymentElementId) { - me.clearPaymentSession(); - } - - me.handleVisibility(); + me.selectedPaymentMeanId = selectedPaymentMeanEl.attr("id"); - var payment = me.getPaymentMethodById(me.selectedPaymentId); + if (!selectedPaymentMeanEl.data("adyen-payment-method")) { + me.checkoutController.unmount(); - // Return & clear when no adyen payment - if (!me.__isAdyenPaymentMethod(payment)) { - me.clearPaymentSession(); return; } - if (!me.__canHandlePayment(payment)) { - me.setPaymentSession(me.__buildMinimalState(payment)); - return; - } - - if (me.__hasActivePaymentMethod()) { - me.enableUpdatePaymentInfoButton(); - return; - } - - $('#' + me.selectedPaymentElementId) - .closest(me.opts.paymentMethodSelector) - .find(me.opts.methodBankdataSelector) - .prop('id', me.getCurrentComponentId(me.selectedPaymentElementId)); - $(me.opts.paymentMethodFormSubmitSelector).addClass('is--disabled'); - me.handleComponent(payment); - }, - setConfig: function () { - var me = this; - - me.fetchAdyenConfig(); - - var adyenPaymentMethodsResponse = me.opts.enrichedPaymentMethods.reduce( - function (rawAdyen, enrichedPaymentMethod) { - var isAdyenPaymentMethod = enrichedPaymentMethod.isAdyenPaymentMethod || false; - if (true === isAdyenPaymentMethod) { - rawAdyen.push(enrichedPaymentMethod.metadata); - } - - return rawAdyen; - }, - [] - ); - - me.adyenConfiguration = { - locale: me.opts.shopLocale, - environment: me.opts.adyenEnvironment, - clientKey: me.opts.adyenClientKey, - paymentMethodsResponse: {'paymentMethods': adyenPaymentMethodsResponse}, - onChange: $.proxy(me.handleOnChange, me), - showPayButton: false - }; - me.saveAdyenConfigInSession(me.adyenConfiguration); - }, - fetchAdyenConfig: function () { - var me = this; - - return $.ajax({ - method: 'GET', - async: false, - dataType: 'json', - url: me.opts.adyenConfigAjaxUrl, - success: function (response) { - if (response['status'] === 'success') { - me.opts.shopLocale = response['shopLocale']; - me.opts.adyenClientKey = response['clientKey']; - me.opts.adyenEnvironment = response['environment']; - me.opts.enrichedPaymentMethods = response['enrichedPaymentMethods']; - me.opts.adyenOrderTotal = response['adyenOrderTotal']; - me.opts.adyenOrderCurrency = response['adyenOrderCurrency']; - } else { - me.addAdyenError(response['content']); - } - - $.loadingIndicator.close(); - } - }); - }, - getCurrentComponentId: function (selectedPaymentElementId) { - return 'component-' + selectedPaymentElementId; - }, - getPaymentMethodById: function (id) { - var me = this; - - return me.opts.enrichedPaymentMethods.filter(function (paymentMethod) { - return paymentMethod.id === id || ( - paymentMethod.isStoredPayment === true - && (paymentMethod.stored_method_id === id || paymentMethod.stored_method_umbrella_id === id)); - })[0] || {}; - }, - /** - * @param {object} paymentMethod - * @param {String} detailKey - * @return {({} | null)} - * @private - */ - __retrievePaymentMethodDetailByKey: function (paymentMethod, detailKey) { - var me = this; - var details = (paymentMethod && paymentMethod.metadata && paymentMethod.metadata.details) || []; - var filteredDetails = details.filter(function (detail) { - return detail.key === detailKey - }); - - return filteredDetails[0] || null; - }, - setCheckout: function () { - var me = this; - - me.adyenCheckout = new AdyenCheckout(me.adyenConfiguration); - }, - handleComponent: function (paymentMethod) { - var me = this; + let adyenPaymentMethodType = selectedPaymentMeanEl.data("adyen-payment-method-type"); - var adyenCheckoutData = me.__buildCheckoutComponentData(paymentMethod); + let componentContainerEl = selectedPaymentMeanEl + .closest(me.opts.paymentMethodBlockSelector) + .find(me.opts.paymentMethodComponentContainerSelector); - if (this.__isPaymentMethodType(paymentMethod, me.opts.applePayType) || - this.__isPaymentMethodType(paymentMethod, me.opts.googlePayType) || - this.__isPaymentMethodType(paymentMethod, me.opts.onlineBankingPLType)) { - me.setConfig(); - me.setPaymentSession(me.__buildMinimalState(paymentMethod)); - me.handleComponentEnableSubmit(); - return; + if (1 === componentContainerEl.length) { + me.checkoutController.mount( + adyenPaymentMethodType, + componentContainerEl[0], + selectedPaymentMeanEl.data("adyen-stored_payment_method_id") + ); } - - me.adyenCheckout - .create(adyenCheckoutData.cardType, adyenCheckoutData.paymentMethodData) - .mount('#' + me.getCurrentComponentId(me.selectedPaymentElementId)); - }, - handleComponentEnableSubmit: function () { - var me = this; - $(me.opts.paymentMethodFormSubmitSelector).removeClass('is--disabled'); }, - handleOnChange: function (state) { - var me = this; - if (state.isValid) { - $(me.opts.paymentMethodFormSubmitSelector).removeClass('is--disabled'); - } else { - $(me.opts.paymentMethodFormSubmitSelector).addClass('is--disabled'); - } - - if (state.isValid && state.data && state.data.paymentMethod) { - me.setPaymentSession(state); - } - - if (me.changeInfosButton) { - me.changeInfosButton.remove(); - me.changeInfosButton = null; - } - }, - handleSelectedMethod: function () { - var me = this; + onPaymentFormSubmit: function (event) { + let me = this, + selectedPaymentMeanEl = $(me.opts.activePaymentMeanSelector).first(); - var form = $(me.opts.formSelector); - var paymentMethodElement = form.find('input[name=payment]:checked'); + if (!me.checkoutController.isPaymentMethodStateValid()) { + event.preventDefault(); + me.checkoutController.showValidation(); - if (!me.isPaymentMethodValid(paymentMethodElement)) { return; } - me.selectedPaymentElementId = paymentMethodElement.attr('id'); - me.selectedPaymentId = paymentMethodElement.val(); + me.updateStoredMethodId(selectedPaymentMeanEl) - // Return when no data has been entered yet + see if component is needed - if (!me.__hasActivePaymentMethod()) { - me.onPaymentChangedAfter(); + if ( + !selectedPaymentMeanEl.data("adyen-payment-method") || + !selectedPaymentMeanEl.data("adyen-stored_payment_method_id") + ) { return; } - me.enableUpdatePaymentInfoButton(); + selectedPaymentMeanEl.val(selectedPaymentMeanEl.data('adyen-payment-mean-id')); }, - isPaymentMethodValid: function (paymentMethodElement) { - var me = this; - if (!paymentMethodElement.length) { - return false; - } - - //Return when no adyen payment - var paymentMethod = me.getPaymentMethodById(paymentMethodElement.val()); - - if (!me.__isAdyenPaymentMethod(paymentMethod)) { - me.clearPaymentSession(); - return false; - } + onPaymentMethodBeforeChange: function (event) { + let me = this, + selectedPaymentMeanEl = $(event.target) + .closest(me.opts.paymentMethodBlockSelector) + .find(me.opts.paymentMeanSelector); - return me.__canHandlePayment(paymentMethod); + me.updateStoredMethodId(selectedPaymentMeanEl); }, - updatePaymentInfo: function () { - var me = this; - - me.removePaymentSession(); - $(me.opts.paymentMethodFormSubmitSelector).addClass('is--disabled'); - var paymentMethod = $(me.opts.formSelector).find('input[name=payment]:checked'); - var payment = me.getPaymentMethodById(paymentMethod.val()); + onShippingBeforeChange: function () { + let me = this, + selectedPaymentMeanEl = $(me.opts.activePaymentMeanSelector).first(); - if (me.__canHandlePayment(payment)) { - $('#' + me.selectedPaymentElementId) - .closest(me.opts.paymentMethodSelector) - .find(me.opts.methodBankdataSelector) - .prop('id', me.getCurrentComponentId(me.selectedPaymentElementId)); + me.updateStoredMethodId(selectedPaymentMeanEl); + }, - me.handleComponent(payment); + updateStoredMethodId: function (selectedPaymentMeanEl) { + let me = this, + storedPaymentMethodEl = $(me.opts.storedPaymentMethodSelector); - if (me.changeInfosButton) { - me.changeInfosButton.remove(); - me.changeInfosButton = null; - } + storedPaymentMethodEl.val(''); + if (selectedPaymentMeanEl.data("adyen-payment-method")) { + storedPaymentMethodEl.val(selectedPaymentMeanEl.data('adyen-stored_payment_method_id')); } }, - setPaymentSession: function (state) { - var me = this; - me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(state.data.paymentMethod)); - me.sessionStorage.setItem(me.storePaymentMethodSession, state.data.storePaymentMethod || false); - }, - getPaymentSession: function () { - var me = this; - return JSON.parse(me.sessionStorage.getItem(me.paymentMethodSession) || "{}"); - }, - clearPaymentSession: function () { - var me = this; - me.sessionStorage.removeItem(me.paymentMethodSession); - me.sessionStorage.removeItem(me.storePaymentMethodSession); - }, - removePaymentSession: function () { - var me = this; - me.clearPaymentSession(); - $.get(me.opts.resetSessionUrl); - }, - saveAdyenConfigInSession: function (adyenConfiguration) { - var me = this; - - var data = { - locale: adyenConfiguration.locale, - environment: adyenConfiguration.environment, - clientKey: adyenConfiguration.clientKey, - paymentMethodsResponse: adyenConfiguration.paymentMethodsResponse - }; - - me.sessionStorage.setItem(me.adyenConfigSession, JSON.stringify(data)); - }, - enableUpdatePaymentInfoButton: function () { - var me = this; - var paymentMethodContainer = $(me.opts.formSelector) - .find('input[name=payment]:checked') - .closest(me.opts.paymentMethodSelector); - if (!paymentMethodContainer) { - return; - } + updateFormSubmitButton: function () { + let me = this, + formSubmit = $(me.opts.formSubmitButtonSelector); - // minimal state has no info that needs updating - var payment = me.getPaymentMethodById(me.selectedPaymentId); - if (me.__hasActiveMinimalPaymentMethodState()) { - return; + if (me.checkoutController.isPaymentMethodStateValid()) { + formSubmit.removeClass('is--disabled'); + } else { + formSubmit.addClass('is--disabled'); } - - me.changeInfosButton = $('') - .addClass(me.opts.classChangePaymentInfo) - .html(me.opts.adyenSnippets.updatePaymentInformation) - .on('click', $.proxy(me.updatePaymentInfo, me)); - paymentMethodContainer - .find(me.opts.methodBankdataSelector) - .append(me.changeInfosButton); }, - /** - * @param {object} paymentMethod - * @return {boolean} - * @private - */ - __isGiftCard: function (paymentMethod) { - var me = this; - return 'giftcard' === paymentMethod.metadata.type; - }, - /** - * @param {object} paymentMethod - * @return {boolean} - * @private - */ - __isStoredPaymentMethod: function (paymentMethod) { - return paymentMethod.isStoredPayment || false; - }, /** + * This button is required because Shopware triggers `plugin/swShippingPayment/onInputChanged` event even for + * shipping methods changes and re-renders the complete page content on the server side. * - * @param {object} paymentMethod - * @param {string} paymentMethodType - * @return {boolean} - * @private - */ - __isPaymentMethodType: function (paymentMethod, paymentMethodType) { - return paymentMethodType === paymentMethod.adyenType; - }, - /** - * @return {boolean} - * @private - */ - __hasActivePaymentMethod: function () { - var sessionPaymentMethod = this.sessionStorage.getItem(this.paymentMethodSession); - if (!sessionPaymentMethod || "{}" === sessionPaymentMethod) { - return false; - } - - return true; - }, - /** - * @return {boolean} - * @private - */ - __hasActiveMinimalPaymentMethodState: function () { - if (!this.__hasActivePaymentMethod()) { - return false; - } - var storedPaymentMethod = this.getPaymentSession(); - var keys = Object.keys(storedPaymentMethod); - - return 1 === keys.length && 'type' === keys[0]; // Minimal state structure @see __buildMinimalState() - }, - /** - * @param {object} paymentMethod - * @return {boolean} - * @private - */ - __isAdyenPaymentMethod: function (paymentMethod) { - return paymentMethod.isAdyenPaymentMethod || false; - }, - /** - * @param {object} paymentMethod - * @return {boolean} - * @private + * In order to avoid the need for the customer to reenter already entered valid payment data we show this button + * if customer explicitly wants to change his data, otherwise, his data is already stored in the session. */ - __canHandlePayment: function (paymentMethod) { - var me = this; + showUpdatePaymentInfoButton: function () { + let me = this; - if (!me.__isAdyenPaymentMethod(paymentMethod)) { - return false; - } - - if (this.__isStoredPaymentMethod(paymentMethod)) { - return true; + if (!me.checkoutController.getPaymentMethodStateData()) { + return; } - // not all adyen payment methods have "details", these cannot be handled by webcomponents (e.g. Paypal) - return "undefined" !== typeof paymentMethod.metadata.details - || "undefined" !== typeof paymentMethod.metadata.brand; - }, - /** - * @param {object} paymentMethod - * @return {boolean} - * @private - */ - __enableStoreDetails: function (paymentMethod) { - return 'scheme' === paymentMethod.adyenType; - }, - /** - * Modify AdyenPaymentMethod with additional data for the web-component library - * @param paymentMethod Shopware PaymentMean enriched with Adyen payment data - * @return {{cardType: string, paymentMethodData: object}} - * @private - */ - __buildCheckoutComponentData: function (paymentMethod) { - var defaultData = { - cardType: paymentMethod.adyenType, - paymentMethodData: paymentMethod.metadata - }; - - if (this.__isStoredPaymentMethod(paymentMethod || {})) { - return $.extend(true, {}, defaultData, { - paymentMethodData: { - storedPaymentMethodId: paymentMethod.metadata.id - } - }); - } + let componentContainerEl = $(me.opts.activePaymentMeanSelector) + .first() + .closest(me.opts.paymentMethodBlockSelector) + .find(me.opts.paymentMethodComponentContainerSelector); - var me = this; - if (this.__isPaymentMethodType(paymentMethod, me.opts.applePayType)) { - return $.extend(true, {}, defaultData, { - paymentMethodData: { - amount: { - 'value': (Number(me.opts.adyenOrderTotal) * 100).toString(), - 'currency': (me.opts.adyenOrderCurrency).toString() - } - } - }); + if (1 !== componentContainerEl.length) { + return; } - if (this.__isGiftCard(paymentMethod)) { - var pinRequiredDetail = this.__retrievePaymentMethodDetailByKey( - paymentMethod, - 'encryptedSecurityCode' - ) || false; - - return $.extend(true, {}, defaultData, { - cardType: 'giftcard', - paymentMethodData: { - type: paymentMethod.adyenType, - brand: paymentMethod.metadata.brand, - pinRequired: pinRequiredDetail && (false !== pinRequiredDetail.optional || false) - } + let updatePaymentInfoButton = $('') + .addClass(me.opts.updatePaymentInfoButtonClass) + .html(me.opts.updatePaymentInfoButtonText) + .on('click', function () { + updatePaymentInfoButton.remove(); + me.selectedPaymentMeanId = null; + me.onPaymentChangedAfter(); }); - } - return $.extend(true, {}, defaultData, { - paymentMethodData: { - enableStoreDetails: this.__enableStoreDetails(paymentMethod) - } - }); - }, - /** - * Create a minimal state when payment is handled by callback (e.g. PayPal payment) - * Use only when web components does NOT handle the payment - * @param payment - * @return {{data: {paymentMethod: {type}}}} - * @private - */ - __buildMinimalState: function (payment) { - return { - data: { - paymentMethod: { - type: payment.adyenType - } - } - }; + componentContainerEl.append(updatePaymentInfoButton); } }); diff --git a/Resources/frontend/js/jquery.adyen-state-data-cleanup.js b/Resources/frontend/js/jquery.adyen-state-data-cleanup.js new file mode 100644 index 00000000..c47e7950 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-state-data-cleanup.js @@ -0,0 +1,27 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-state-data-cleanup', { + /** + * Plugin default options. + */ + defaults: { + checkoutConfigUrl: '', + }, + + init: function () { + let me = this; + + me.applyDataAttributes(); + + let checkoutController = new AdyenComponents.CheckoutController({ + "checkoutConfigUrl": me.opts.checkoutConfigUrl, + "sessionStorage": StorageManager.getStorage('session') + }); + + checkoutController.unmount(); + } + }); + +})(jQuery); + diff --git a/Resources/frontend/js/jquery.plugin-loader.js b/Resources/frontend/js/jquery.plugin-loader.js index 8e2c14af..faf2b9ef 100644 --- a/Resources/frontend/js/jquery.plugin-loader.js +++ b/Resources/frontend/js/jquery.plugin-loader.js @@ -1,11 +1,22 @@ ;(function ($) { 'use strict'; + $(function () { StateManager - .addPlugin('.adyen-payment-selection', 'adyen-payment-selection') - .addPlugin('*[data-adyen-checkout-error="true"]', 'adyen-checkout-error') - .addPlugin('.is--act-confirm', 'adyen-confirm-order') - .addPlugin('.is--act-finish', 'adyen-finish-order') - .addPlugin('[data-adyen-disable-payment]', 'adyen-disable-payment'); + .addPlugin('[data-adyen-payment-selection]', 'adyen-payment-selection') + .addPlugin('[data-adyen-confirm-order]', 'adyen-confirm-order') + .addPlugin('[data-adyen-express-checkout]', 'adyen-express-checkout') + .addPlugin('input[name=adyenPaymentMethodStateData]', 'adyen-payment-method-state-data-setter') + .addPlugin('[data-adyen-payment-additional-action]', 'adyen-payment-additional-action') + .addPlugin('#shipping_payment_wrapper', 'adyen-cookie-consent-visibility-handler') + .addPlugin('body:not(.is--ctl-checkout)', 'adyen-state-data-cleanup') + .addPlugin('.is--act-finish', 'adyen-state-data-cleanup') + .addPlugin('[data-adyen-disable-payment]', 'adyen-disable-payment') + .addPlugin('#donation-container', 'adyen-donations'); + + $.subscribe('plugin/swAjaxVariant/onRequestData', function () { + StateManager + .addPlugin('[data-adyen-express-checkout]', 'adyen-express-checkout'); + }); }); })(jQuery); diff --git a/Resources/frontend/less/all.less b/Resources/frontend/less/all.less index cab1f7fd..2d1a78c1 100644 --- a/Resources/frontend/less/all.less +++ b/Resources/frontend/less/all.less @@ -25,6 +25,11 @@ } } +.adyen-additional-action-container { + display: flex; + justify-content: center; +} + .adyen-modal .content { padding: 20px; @@ -61,3 +66,7 @@ display: none; } } + +form.buybox--form { + padding: 0 0 .625rem 0; +} diff --git a/Resources/menu.xml b/Resources/menu.xml index 91403fae..58090ad9 100644 --- a/Resources/menu.xml +++ b/Resources/menu.xml @@ -1,14 +1,14 @@ - + - AdyenPaymentNotificationsListingExtension - - AdyenPaymentNotificationsListingExtension + AdyenPaymentConfiguration + + AdyenPaymentMain index - sprite-mail-share + adyen-icon ConfigurationMenu + Shopware.ModuleManager.createSimplifiedModule("AdyenPaymentMain", { "title": "Adyen", maximized: true }) diff --git a/Resources/services/adyen-api.xml b/Resources/services/adyen-api.xml deleted file mode 100644 index 0239d3d0..00000000 --- a/Resources/services/adyen-api.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Resources/services/adyen.xml b/Resources/services/adyen.xml deleted file mode 100644 index 9a112001..00000000 --- a/Resources/services/adyen.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/Resources/services/applepay-merchant.xml b/Resources/services/applepay-merchant.xml deleted file mode 100644 index 05ae7875..00000000 --- a/Resources/services/applepay-merchant.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - https://eu.adyen.link - %kernel.root_dir%/.well-known/apple-developer-merchantid-domain-association - %adyen_payment.plugin_dir%/storage/apple-developer-merchantid-domain-association.archive - - - - - - %apple_pay.merchant_association.storage_path% - - - - - - - - - - - - - - - - - - - - %apple_pay.merchant_association.base_uri% - - - %apple_pay.merchant_association.archive_path% - - - - diff --git a/Resources/services/basket.xml b/Resources/services/basket.xml deleted file mode 100644 index 3b179e87..00000000 --- a/Resources/services/basket.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/Resources/services/commands.xml b/Resources/services/commands.xml deleted file mode 100644 index 5bd96ea6..00000000 --- a/Resources/services/commands.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/Resources/services/components.xml b/Resources/services/components.xml index 1a51a87f..b7973039 100644 --- a/Resources/services/components.xml +++ b/Resources/services/components.xml @@ -1,181 +1,46 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - - + + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + - - - - - + + + - - - - - - + + service('models').getRepository('Shopware\\Models\\Order\\Order') - - - + + service('models').getRepository('Shopware\\Models\\Shop\\Shop') - - - + + service('models').getRepository('Shopware\\Models\\Payment\\Payment') - - - - + + + - - - + + container.get('modules').Basket() + + - - - + + - diff --git a/Resources/services/dbal.xml b/Resources/services/dbal.xml deleted file mode 100644 index 3f208908..00000000 --- a/Resources/services/dbal.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Resources/services/doctrine.xml b/Resources/services/doctrine.xml deleted file mode 100644 index b9a749b5..00000000 --- a/Resources/services/doctrine.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/services/enrichers.xml b/Resources/services/enrichers.xml deleted file mode 100644 index e7dba54d..00000000 --- a/Resources/services/enrichers.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/Resources/services/factory.xml b/Resources/services/factory.xml deleted file mode 100644 index 8d16e2ea..00000000 --- a/Resources/services/factory.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - service('models').getRepository('Shopware\\Models\\Country\\Country') - - - - diff --git a/Resources/services/http.xml b/Resources/services/http.xml deleted file mode 100644 index fa292bd8..00000000 --- a/Resources/services/http.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/Resources/services/importers.xml b/Resources/services/importers.xml deleted file mode 100644 index 7f90f5f6..00000000 --- a/Resources/services/importers.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - service('models').getRepository('Shopware\\Models\\Shop\\Shop') - - - - - - - - - - - diff --git a/Resources/services/logger.xml b/Resources/services/logger.xml new file mode 100644 index 00000000..e3a4360b --- /dev/null +++ b/Resources/services/logger.xml @@ -0,0 +1,14 @@ + + + + Monolog\Logger::DEBUG + + + + + + + diff --git a/Resources/services/loggers.xml b/Resources/services/loggers.xml deleted file mode 100644 index 0fc96f39..00000000 --- a/Resources/services/loggers.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - adyen_payment_notifications - - - - - - - adyen - - - - - - - - %kernel.logs_dir%/adyen_payment_notifications_%kernel.environment%.log - 14 - %kernel.default_error_level% - - - - - - - %kernel.logs_dir%/adyen_%kernel.environment%.log - 14 - %kernel.default_error_level% - - - - - - \ No newline at end of file diff --git a/Resources/services/managers.xml b/Resources/services/managers.xml deleted file mode 100644 index 2f0a9ffb..00000000 --- a/Resources/services/managers.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/services/payload-providers.xml b/Resources/services/payload-providers.xml deleted file mode 100644 index 270b5c2c..00000000 --- a/Resources/services/payload-providers.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/services/providers.xml b/Resources/services/providers.xml deleted file mode 100644 index ad4a1fa6..00000000 --- a/Resources/services/providers.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/Resources/services/repositories.xml b/Resources/services/repositories.xml deleted file mode 100644 index aae2f781..00000000 --- a/Resources/services/repositories.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - service('models').getRepository('AdyenPayment\Models\RecurringPayment\RecurringPaymentToken') - - - - - - - - - - - - - diff --git a/Resources/services/rules.xml b/Resources/services/rules.xml deleted file mode 100644 index ffa280fb..00000000 --- a/Resources/services/rules.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - service('models').getRepository('Shopware\\Models\\Shop\\Shop') - - - - - - - - - - - - - - - - diff --git a/Resources/services/serializers.xml b/Resources/services/serializers.xml deleted file mode 100644 index 88706efb..00000000 --- a/Resources/services/serializers.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/Resources/services/session.xml b/Resources/services/session.xml deleted file mode 100644 index cc51709a..00000000 --- a/Resources/services/session.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/Resources/services/shopware.xml b/Resources/services/shopware.xml deleted file mode 100644 index fc0a112f..00000000 --- a/Resources/services/shopware.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Resources/services/subscribers-cronjob.xml b/Resources/services/subscribers-cronjob.xml deleted file mode 100644 index 1c885ead..00000000 --- a/Resources/services/subscribers-cronjob.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/services/subscribers.xml b/Resources/services/subscribers.xml index e6013c7f..212c111b 100644 --- a/Resources/services/subscribers.xml +++ b/Resources/services/subscribers.xml @@ -1,133 +1,93 @@ - + + - - - - - + %adyen_payment.plugin_dir% - service('models').getRepository('AdyenPayment\\Models\\Notification') - - - - service('models').getRepository('AdyenPayment\\Models\\PaymentInfo') - - + - - service('models').getRepository('Shopware\\Models\\Shop\\Shop') - - - + + %adyen_payment.plugin_dir% + + - - - + - - - - %adyen_payment.plugin_dir% - - - - %adyen_payment.plugin_dir% - + + - - - + - + + - - - - - + - - - - - - - - - - + + - - + + + - - + + - - + - - + + + service('models').getRepository('AdyenPayment\\Models\\UserPreference') - - + + + service('models').getRepository('AdyenPayment\\Models\\UserPreference') + - - + + + - - + - - - - + - - - + - - - + - + + - - - - service('models').getRepository('AdyenPayment\\Models\\UserPreference') - + + - - - service('models').getRepository('AdyenPayment\\Models\\UserPreference') + diff --git a/Resources/services/validators.xml b/Resources/services/validators.xml deleted file mode 100644 index 07c15785..00000000 --- a/Resources/services/validators.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - service('models').getRepository('Shopware\\Models\\Shop\\Shop') - - - diff --git a/Resources/services/web-components.xml b/Resources/services/web-components.xml deleted file mode 100644 index f3d359fb..00000000 --- a/Resources/services/web-components.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/Resources/snippets/backend/adyen/configuration.ini b/Resources/snippets/backend/adyen/configuration.ini new file mode 100644 index 00000000..870a0e67 --- /dev/null +++ b/Resources/snippets/backend/adyen/configuration.ini @@ -0,0 +1,30 @@ +[default] +payment/adyen/status/pending = "Pending" +payment/adyen/update/api_key_warning_title = "Insufficient scope detected (%s)" +payment/adyen/update/api_key_warning_description = "Please reauthenticate with a new API key in order to continue using the Adyen plugin seamlessly" +payment/adyen/update/api_key_warning_open_button_text = "Open Adyen" +payment/adyen/update/transaction_history_info_title = "Migration started" +payment/adyen/update/transaction_history_info_description = "The migration of existing Adyen transactions has started in the background" +payment/adyen/detail/pspreference = "PSP Reference" +payment/adyen/detail/date = "Date" +payment/adyen/detail/eventcode = "Event code" +payment/adyen/detail/merchant = "Merchant" +payment/adyen/detail/paymentmethod = "Payment method" +payment/adyen/detail/currency = "Currency" +payment/adyen/detail/status = "Status" +payment/adyen/detail/success = "Success" +payment/adyen/detail/riskscore = "Risk score" +payment/adyen/detail/paidamount = "Paid amount" +payment/adyen/detail/refundedamount = "Refunded amount" +payment/adyen/detail/cancel = "Cancel" +payment/adyen/detail/capture = "Capture" +payment/adyen/detail/refund = "Refund" +payment/adyen/detail/viewonadyenca = "View payment on Adyen CA" +payment/adyen/detail/transaction = "Transaction" +payment/adyen/detail/datetime = "Date & time" +payment/adyen/cancelrequestfail = "Cancellation request failed. Please check Adyen configuration. Reason: " +payment/adyen/refundrequestfail = "Refund request failed. Please check Adyen configuration. Reason: " +payment/adyen/capturerequestfail = "Capture request failed. Please check Adyen configuration. Reason: " + +[de_DE: default] +payment/adyen/status/pending = "Ausstehend" diff --git a/Resources/snippets/frontend/adyen/checkout.ini b/Resources/snippets/frontend/adyen/checkout.ini new file mode 100644 index 00000000..987580a2 --- /dev/null +++ b/Resources/snippets/frontend/adyen/checkout.ini @@ -0,0 +1,17 @@ +[default] +payment/adyen/unrecognized_payment_method = "Unrecognized payment method. Please select valid payment method from the list." +payment/adyen/payment_processing_error = "Your payment could not be processed, please resubmit order." +payment/adyen/update_payment_info_button_text = "Update your payment information" +payment/adyen/card_number_ending_on = "Card number ending on: %s" +payment/adyen/stored_payment_methods_title = "Stored payment methods" +payment/adyen/payment_methods_title = "Payment methods" +payment/adyen/disable_action_error = "Disable action could not be processed, invalid request." +payment/adyen/user_not_found = "Disable action could not be processed, user not found." +payment/adyen/disable = "Disable" +payment/adyen/disable_confirm_message = "Are you sure to remove the stored payment method?" +payment/adyen/confirm = "Confirm" +payment/adyen/cancel = "Cancel" +donations/adyen/fail = "Donation failed." +donations/adyen/success = "Donation successfully made." + +[de_DE: default] diff --git a/Resources/views/backend/_base/layout.tpl b/Resources/views/backend/_base/layout.tpl new file mode 100644 index 00000000..fc1012cd --- /dev/null +++ b/Resources/views/backend/_base/layout.tpl @@ -0,0 +1,18 @@ + + + + {s name="main/title"}Adyen{/s} + + + + + {block name="styles"}{/block} + {block name="scripts"}{/block} + + + +{block name="content/main"}{/block} + +{block name="content/javascript"}{/block} + + diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff new file mode 100644 index 00000000..33eb3655 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff2 new file mode 100644 index 00000000..f43019ac Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Blond.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff new file mode 100644 index 00000000..e69558e9 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff2 new file mode 100644 index 00000000..4840b751 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-BlondItalic.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff new file mode 100644 index 00000000..3eb11210 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff2 new file mode 100644 index 00000000..92b8df54 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-Normal.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff new file mode 100644 index 00000000..0ffd68fa Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff2 new file mode 100644 index 00000000..e36a8c73 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-NormalItalic.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff new file mode 100644 index 00000000..cf37107a Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff2 new file mode 100644 index 00000000..f363f8dc Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBold.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff new file mode 100644 index 00000000..dbce56b3 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff2 b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff2 new file mode 100644 index 00000000..7f9d6221 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/Fakt/Fakt-SemiBoldItalic.woff2 differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.ttf b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.ttf new file mode 100644 index 00000000..283ba052 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.woff b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.woff new file mode 100644 index 00000000..79a9a806 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Blond.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.ttf b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.ttf new file mode 100644 index 00000000..52b94d1f Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.woff b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.woff new file mode 100644 index 00000000..0d4a9490 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-BlondItalic.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.ttf b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.ttf new file mode 100644 index 00000000..481a4933 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.woff b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.woff new file mode 100644 index 00000000..ecc1b244 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-Normal.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.ttf b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.ttf new file mode 100644 index 00000000..e322eed4 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.woff b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.woff new file mode 100644 index 00000000..a46acecf Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-NormalItalic.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.ttf b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.ttf new file mode 100644 index 00000000..0142986a Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.woff b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.woff new file mode 100644 index 00000000..1e18a1e6 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/FaktPro/FaktPro-SemiBold.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.eot b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.eot new file mode 100644 index 00000000..aa2f9d6a Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.eot differ diff --git a/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.svg b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.svg new file mode 100644 index 00000000..4c877626 --- /dev/null +++ b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.svg @@ -0,0 +1,36 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.ttf b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.ttf new file mode 100644 index 00000000..230d044e Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.woff b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.woff new file mode 100644 index 00000000..32ef04db Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/ad-icons/ad-icons.woff differ diff --git a/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.eot b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.eot new file mode 100644 index 00000000..dbe9f4d0 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.eot differ diff --git a/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.svg b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.svg new file mode 100644 index 00000000..75ba7777 --- /dev/null +++ b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.svg @@ -0,0 +1,37 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.ttf b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.ttf new file mode 100644 index 00000000..60afff95 Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.ttf differ diff --git a/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.woff b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.woff new file mode 100644 index 00000000..6e247cec Binary files /dev/null and b/Resources/views/backend/_resources/assets/fonts/adl-icons/adl-icons.woff differ diff --git a/Resources/views/backend/_resources/css/adyen-core.css b/Resources/views/backend/_resources/css/adyen-core.css new file mode 100644 index 00000000..e6c54e84 --- /dev/null +++ b/Resources/views/backend/_resources/css/adyen-core.css @@ -0,0 +1,2916 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; + min-height: 100%; + max-height: 100%; + height: 100%; + background-color: white; + font-size: 100%; + text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +/* @doc(group) { name: 'sass-extend', title: 'Sass extend' } */ /* @doc(group) { name: 'helpers', title: 'Helpers'} */ +/* @doc(function) { + description: 'Concatenate strings', + return: 'string that is joined from items', + params: { + $separator: 'string to use when concatenating values', + $items: 'item csv list to concatenate', + } +} +*/ +/* @doc(group) { name: 'map', title: 'Map'} */ +/* @doc(function) { + description: 'Check if variable is map' +} +*/ +/* @doc(function) {} */ +/* @doc(function) { + description: 'Get items from map', + return: 'map item value', +} +*/ +/* @doc(function) { + description: 'Merge second map into the first map', + return: 'new map which is result of the merge', +} +*/ +table { + width: 100%; + border-collapse: collapse; +} +table thead tr th { + border-bottom: 1px solid #dce0e5; +} +table tr:not(:last-of-type) td { + border-bottom: 1px solid #dce0e5; +} +table tr th, table tr td { + text-align: center; + padding: 14px; +} +table tr th.adlm--left-aligned, table tr td.adlm--left-aligned { + text-align: left; +} +table tr th.adlm--blue-text, table tr td.adlm--blue-text { + color: #0066ff; +} +table tr td { + font-weight: 300; +} +table tr td:last-of-type { + white-space: nowrap; +} +table tr th { + font-weight: 600; +} + +ul.adl-bullet-list { + list-style: disc; + padding-left: 15px; + margin-top: 16px; +} +ul.adl-bullet-list li { + padding-left: 5px; +} + +#adl-page { + position: relative; + display: flex; + flex-direction: column; + column-gap: 120px; + overflow: hidden; + background-color: #fff; + width: 100%; + max-height: 100%; + padding: 24px 12px 24px 24px; +} +@media (min-width: 1281px) { + #adl-page { + flex-direction: row; + } +} +@media (max-width: 767.98px) { + #adl-page { + padding: 12px; + } +} +#adl-page > .adl-sidebar { + display: flex; + flex-direction: column; + padding-top: 24px; +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar { + z-index: 11; + } +} +#adl-page > .adl-sidebar span, #adl-page > .adl-sidebar a, #adl-page > .adl-sidebar h3 { + white-space: nowrap; +} +#adl-page > .adl-sidebar .adlp-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 48px; + padding-right: 24px; +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar .adlp-header { + padding-right: 0; + } +} +#adl-page > .adl-sidebar .adlp-mobile-menu { + display: none; +} +#adl-page > .adl-sidebar .adlp-mobile-menu:before { + font-family: "adl-icons"; + content: "\e902"; + line-height: 1; + font-weight: 500; + font-size: 24px; +} +#adl-page > .adl-sidebar .adlp-mobile-menu:focus { + box-shadow: none; +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar .adlp-mobile-menu { + display: block; + } +} +#adl-page > .adl-sidebar .adlp-mobile-underlay { + transform: translateX(130%); + position: absolute; + opacity: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 17, 44, 0.4); + z-index: 1; + transition: opacity 0.3s ease; +} +#adl-page > .adl-sidebar .adl-logo { + display: flex; + gap: 12px; +} +#adl-page > .adl-sidebar .adlp-version-info { + background: rgba(10, 191, 83, 0.12); + color: #08A648; + border-radius: 6px; + padding: 6px 12px; + font-size: 13px; + font-weight: 600; +} +#adl-page > .adl-sidebar .adlp-version-info.adls--warning { + background: #FFEACC; + color: #7F4A00; +} +#adl-page > .adl-sidebar .adlp-version-info.adls--warning:hover .adlp-tooltip { + display: block; +} +#adl-page > .adl-sidebar .adlp-body { + overflow: auto; + flex-grow: 1; + max-height: 100%; + padding-bottom: 80px; +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar .adlp-body { + pointer-events: none; + transform: translateX(130%); + transition: all 0.3s ease; + position: absolute; + z-index: 2; + right: 0; + background: #fff; + border-radius: 8px 0 0 0; + padding: 44px; + top: 70px; + bottom: 0; + } +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu { + color: #394962; + font-size: 14px; + line-height: 18px; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li h3 { + text-transform: uppercase; + border-bottom: none; + font-weight: 600; + margin-bottom: 8px; + background-color: transparent; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul { + margin-bottom: 24px; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul .adlp-menu-item.adls--disabled { + pointer-events: none; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul a, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul button { + display: block; + padding: 8px 8px 8px 24px; + border: none; + background: none; + border-radius: 6px; + margin-bottom: 2px; + font-size: inherit; + color: inherit; + font-weight: 400; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul a:hover, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul button:hover { + background: #f3f6f9; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul a.adls--active, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul button.adls--active { + background: #f3f6f9; + font-weight: 600; +} +#adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul a:disabled, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul a.adls--disabled, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul button:disabled, #adl-page > .adl-sidebar .adlp-body .adlp-menu > li > ul button.adls--disabled { + pointer-events: none; + cursor: not-allowed; +} +#adl-page > .adl-sidebar .adlp-quick-links { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 15px; + line-height: 21px; + color: #20304c; +} +#adl-page > .adl-sidebar .adlp-quick-links h3 { + font-weight: 600; + margin: 8px 0; +} +#adl-page > .adl-sidebar .adlp-quick-links a { + align-self: flex-start; +} +#adl-page > .adl-sidebar.adls--menu-active .adlp-mobile-menu { + z-index: 2; + color: #fff; +} +#adl-page > .adl-sidebar.adls--menu-active .adlp-mobile-menu:before { + content: "\e90a"; +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar.adls--menu-active .adlp-mobile-underlay { + opacity: 1; + transform: translateX(0); + } + #adl-page > .adl-sidebar.adls--menu-active .adlp-body { + transform: translateX(0); + pointer-events: auto; + } + #adl-page > .adl-sidebar.adls--menu-active .adlp-body > ul { + min-width: 330px; + } +} +@media (max-width: 767.98px) { + #adl-page > .adl-sidebar.adls--menu-active .adlp-mobile-menu { + color: #000; + } + #adl-page > .adl-sidebar.adls--menu-active .adlp-mobile-underlay { + opacity: 0; + } + #adl-page > .adl-sidebar.adls--menu-active .adlp-body { + scale: 1; + opacity: 1; + transform: none; + width: 100%; + max-height: calc(100vh - 70px); + } +} +@media (max-width: 1280.98px) { + #adl-page > .adl-sidebar { + padding: 12px 12px 0; + } +} +#adl-page > main { + flex-grow: 1; + width: 100%; + max-height: 100%; + overflow: auto; + padding-right: 24px; +} +#adl-page > main .adlp-content-holder { + max-width: 1300px; + display: flex; + flex-direction: column; + column-gap: 24px; + row-gap: 24px; + margin: 0 auto; + padding-left: 5px; + padding-bottom: 260px; + padding-right: 12px; +} +@media (max-width: 767.98px) { + #adl-page > main .adlp-content-holder { + padding-right: 0; + } +} +@media (max-width: 1280.98px) { + #adl-page > main { + max-width: 100%; + padding-right: 5px; + } +} + +.adl-hint { + position: relative; +} +.adl-hint .adlp-tooltip { + display: none; + position: absolute; + width: max-content; + max-width: 300px; +} +.adl-hint .adlp-tooltip.adlt--bottom { + top: calc(100% + 5px); + padding: 4px 7px; + background: #20304c; + color: #fff; + border-radius: 4px; + left: 50%; + transform: translateX(-50%); +} +.adl-hint .adlp-tooltip.adlt--bottom:before { + content: ""; + display: block; + position: absolute; + width: 6px; + height: 6px; + background: #20304c; + transform: rotate(45deg); + border-radius: 2px; + top: -3px; + left: calc(50% - 3px); +} +.adl-hint .adlp-tooltip.adlt--top { + bottom: calc(100% + 10px); + padding: 16px; + background: #fff; + color: #00112c; + border: 1px solid #dce0e5; + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); + border-radius: 4px; + left: 50%; + transform: translateX(-50%); +} +.adl-hint .adlp-tooltip.adlt--top:after { + content: ""; + display: block; + position: absolute; + width: 16px; + height: 16px; + background: #fff; + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); + transform: rotate(45deg); + border-radius: 2px; + bottom: -7px; + left: calc(50% - 10px); + clip-path: inset(0px -10px -10px 0); +} +.adl-hint.adls--active .adlp-tooltip { + display: block; +} + +.adl-file-drop-zone { + border: 1px dashed #a5afbd; + border-radius: 4px; + padding: 24px 36px; + width: fit-content; + color: #69778c; +} +.adl-file-drop-zone.adls--active { + border-color: #cef2dd; +} +.adl-file-drop-zone .adlp-input-file-label input[type=file] { + display: none; +} +.adl-file-drop-zone .adlp-file-label { + display: flex; + column-gap: 12px; + cursor: pointer; +} +.adl-file-drop-zone .adlp-file-label img { + max-width: 50px; + max-height: 25px; + object-fit: contain; +} +.adl-file-drop-zone .adlp-file-label.adls--empty:before { + font-family: "adl-icons"; + content: "\e909"; + line-height: 1; + font-weight: 500; + font-size: 24px; +} + +.adl-form-footer { + position: absolute; + bottom: 0; + width: 100%; + left: 0; + padding: 14px 48px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 12px; + background: #fff; + box-shadow: 0px -5px 0px rgba(105, 119, 140, 0.1), inset 0px 1px 0px #dce0e5; + z-index: 10; +} +.adl-form-footer .adlp-changes-count { + opacity: 0; + color: #00112c; + font-size: 15px; +} +.adl-form-footer .adlp-changes-count.adls--active { + opacity: 1; +} +.adl-form-footer .adlp-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +@media (max-width: 767.98px) { + .adl-form-footer { + padding: 14px; + } + .adl-form-footer .adlp-changes-count { + font-size: 13px; + } + .adl-form-footer .adl-button span { + white-space: nowrap; + } +} + +.adl-confirm-modal .adlp-modal-content { + max-width: 600px; +} +.adl-confirm-modal .adlp-modal-content .adlp-body { + padding: 24px 24px 40px; + font-size: 16px; +} +.adl-confirm-modal .adlp-modal-content ul { + list-style: disc; + padding-left: 15px; + margin-top: 16px; +} +.adl-confirm-modal .adlp-modal-content ul li { + padding-left: 5px; +} + +.adlp-merchant-account-description { + margin-bottom: -12px; +} + +.adl-notifications-page .adl-notifications-table .adl-table-wrapper { + max-height: 585px; + overflow-y: auto; +} +@media (max-width: 1280.98px) { + .adl-notifications-page .adl-notifications-table .adl-table-wrapper { + display: block; + } + .adl-notifications-page .adl-notifications-table .adl-table-wrapper table { + overflow-x: auto; + } +} +.adl-notifications-page .adl-notifications-table .adl-table-wrapper table tr td { + padding: 16px 7px 19px 16px; + font-size: 13px; +} +.adl-notifications-page .adl-notifications-table .adl-table-wrapper table tr td:last-of-type { + white-space: normal; +} +.adl-notifications-page .adl-notifications-table .adl-table-wrapper .adl-button.adlt--primary { + display: initial; + padding: 0; + text-align: left; +} +.adl-notifications-page .adl-notifications-table .adl-table-wrapper .adl-button.adlt--primary:hover, .adl-notifications-page .adl-notifications-table .adl-table-wrapper .adl-button.adlt--primary:focus { + background: inherit; + box-shadow: none; + text-decoration: underline; +} +.adl-notifications-page .adl-notifications-table .adl-table-wrapper .adlp-payment-logo { + max-width: 25px; + max-height: 25px; + margin: 0; +} + +.adl-modal.adl-webhook-notifications-modal .adlp-body { + overflow: initial; + font-size: 14px; +} +.adl-modal.adl-webhook-notifications-modal.adls--full-height { + max-height: initial; +} +.adl-modal.adl-webhook-notifications-modal .adlp-title { + padding: 16px 24px 31px; +} +.adl-modal.adl-webhook-notifications-modal .adlp-reason { + margin-bottom: 12px; +} +.adl-modal.adl-webhook-notifications-modal .adlp-failure-description { + margin-bottom: 27px; +} +.adl-modal.adl-webhook-notifications-modal .adlp-reason-title, .adl-modal.adl-webhook-notifications-modal .adlp-failure-description-title { + font-weight: 600; +} +.adl-modal.adl-webhook-notifications-modal .adlp-modal-content { + max-width: 515px; +} +.adl-modal.adl-webhook-notifications-modal .adlp-adyen-link, +.adl-modal.adl-webhook-notifications-modal .adlp-shop-link { + width: 100%; + font-size: 14px; + margin-bottom: 18px; +} + +.adlp-system-info-settings .adlp-setting-field button, .adlp-system-info-settings .adlp-setting-field a { + width: fit-content; +} +.adlp-system-info-settings .adlp-contact-adyen-support a, .adlp-system-info-settings .adlp-download-report a { + position: relative; + display: inline-block; + padding: 10px 16px; + font-weight: 600; + font-size: 15px; + cursor: pointer; + transition: all 0.2s; + border-radius: 6px; + margin-right: 0; + padding: 6px 16px; + border: 1px solid #dce0e5; + color: #394962; + font-weight: 600; +} +.adlp-system-info-settings .adlp-contact-adyen-support a:focus, .adlp-system-info-settings .adlp-download-report a:focus { + box-shadow: 0 0 0 3px #cce0ff; +} +.adlp-system-info-settings .adlp-contact-adyen-support a span, .adlp-system-info-settings .adlp-download-report a span { + display: flex; + align-items: center; + column-gap: 8px; +} +.adlp-system-info-settings .adlp-contact-adyen-support a span:before, .adlp-system-info-settings .adlp-contact-adyen-support a span:after, .adlp-system-info-settings .adlp-download-report a span:before, .adlp-system-info-settings .adlp-download-report a span:after { + font-family: "adl-icons"; + line-height: 1; + font-weight: 500; + font-size: inherit; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlm--full-width, .adlp-system-info-settings .adlp-download-report a.adlm--full-width { + width: 100%; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlm--small, .adlp-system-info-settings .adlp-download-report a.adlm--small { + padding: 4px 8px; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlm--medium, .adlp-system-info-settings .adlp-download-report a.adlm--medium { + padding: 6px 16px; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary, .adlp-system-info-settings .adlp-download-report a.adlt--primary { + background-color: #0066ff; + color: #fff; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary.adlm--destructive, .adlp-system-info-settings .adlp-download-report a.adlt--primary.adlm--destructive { + background-color: #e50000; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary.adls--disabled, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary:disabled, .adlp-system-info-settings .adlp-download-report a.adlt--primary.adls--disabled, .adlp-system-info-settings .adlp-download-report a.adlt--primary:disabled { + background-color: #dce0e5; + color: #a5afbd; + pointer-events: none; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary:hover, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--primary:focus, .adlp-system-info-settings .adlp-download-report a.adlt--primary:hover, .adlp-system-info-settings .adlp-download-report a.adlt--primary:focus { + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary, .adlp-system-info-settings .adlp-download-report a.adlt--secondary { + border: 1px solid #dce0e5; + background-color: #fff; + color: #394962; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary:hover, .adlp-system-info-settings .adlp-download-report a.adlt--secondary:hover { + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary.adlm--destructive, .adlp-system-info-settings .adlp-download-report a.adlt--secondary.adlm--destructive { + color: #e50000; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary.adlm--blue, .adlp-system-info-settings .adlp-download-report a.adlt--secondary.adlm--blue { + color: #0066ff; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary.adls--disabled, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--secondary:disabled, .adlp-system-info-settings .adlp-download-report a.adlt--secondary.adls--disabled, .adlp-system-info-settings .adlp-download-report a.adlt--secondary:disabled { + color: #a5afbd; + pointer-events: none; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost, .adlp-system-info-settings .adlp-download-report a.adlt--ghost { + border: none; + background-color: transparent; + color: #394962; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost:hover, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost:focus, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost:active, .adlp-system-info-settings .adlp-download-report a.adlt--ghost:hover, .adlp-system-info-settings .adlp-download-report a.adlt--ghost:focus, .adlp-system-info-settings .adlp-download-report a.adlt--ghost:active { + background-color: #fff; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost.adlm--blue, .adlp-system-info-settings .adlp-download-report a.adlt--ghost.adlm--blue { + color: #0066ff; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost.adlm--destructive, .adlp-system-info-settings .adlp-download-report a.adlt--ghost.adlm--destructive { + color: #e50000; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost.adls--disabled, .adlp-system-info-settings .adlp-contact-adyen-support a.adlt--ghost:disabled, .adlp-system-info-settings .adlp-download-report a.adlt--ghost.adls--disabled, .adlp-system-info-settings .adlp-download-report a.adlt--ghost:disabled { + background-color: #dce0e5; + color: #a5afbd; + pointer-events: none; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlm--icon-only, .adlp-system-info-settings .adlp-download-report a.adlm--icon-only { + padding: 10px; +} +.adlp-system-info-settings .adlp-contact-adyen-support a.adlm--icon-only span, .adlp-system-info-settings .adlp-download-report a.adlm--icon-only span { + display: none; +} +.adlp-system-info-settings .adlp-contact-adyen-support a:after, .adlp-system-info-settings .adlp-download-report a:after { + display: none; +} + +.adlp-no-items-wrapper { + margin: 80px auto; + max-width: 330px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 15px; +} +.adlp-no-items-wrapper p { + text-align: center; +} +@media (max-width: 767.98px) { + .adlp-no-items-wrapper { + margin: 50px auto; + } +} + +.adl-multiselect-filter { + position: relative; + width: fit-content; +} +.adl-multiselect-filter .adlp-filter-button { + padding: 0 16px; + height: 32px; + display: inline-flex; + align-items: center; + font-weight: 400; +} +.adl-multiselect-filter .adlp-filter-button .adlp-tooltip { + position: absolute; + top: calc(-100% - 3px); + white-space: nowrap; + background-color: #20304c; + color: #fff; + font-weight: 600; + left: 50%; + transform: translateX(-50%); + font-size: 13px; + padding: 4px 8px; + border-radius: 4px; + display: none; +} +.adl-multiselect-filter .adlp-filter-button .adlp-tooltip:after { + content: ""; + display: block; + position: absolute; + width: 10px; + height: 10px; + background: #00112c; + transform: rotate(45deg); + border-radius: 2px; + bottom: -5px; + left: calc(50% - 5px); +} +.adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button { + display: none; + border-radius: 50%; + width: 24px; + height: 24px; +} +.adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:after { + font-family: "adl-icons"; + content: "\e90a"; + line-height: 1; + font-weight: 500; + font-size: 9px; + font-weight: 600; + color: #fff; +} +.adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:hover { + background: #fff; +} +.adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:hover:after { + color: #20304c; +} +.adl-multiselect-filter .adlp-filter-button.adls--selected { + display: inline-flex; + align-items: center; + padding: 0 8px 0 16px; + gap: 8px; + background-color: #20304c; + color: #fff; + border-color: transparent; + font-weight: 600; +} +.adl-multiselect-filter .adlp-filter-button.adls--selected:hover .adlp-tooltip { + display: block; +} +.adl-multiselect-filter .adlp-filter-button.adls--selected .adlp-delete-text-button { + display: inline-block; +} +.adl-multiselect-filter .adl-single-select-dropdown .adlp-dropdown-list-item.adls--selected:after { + display: none; +} +.adl-multiselect-filter .adlp-dropdown-container { + display: none; + position: absolute; + top: 48px; + z-index: 1; +} +.adl-multiselect-filter .adlp-dropdown-container .adlp-content { + width: 350px; + border-radius: 6px; + border: 1px solid #dce0e5; + box-shadow: 0 8px 16px rgba(0, 17, 44, 0.1); + background-color: #fff; +} +.adl-multiselect-filter .adlp-dropdown-container.adls--open { + display: block; +} +@media (max-width: 767.98px) { + .adl-multiselect-filter .adlp-dropdown-container.adls--open { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 17, 44, 0.4); + } +} +.adl-multiselect-filter .adlp-filter-header { + position: relative; + display: none; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 16px; +} +@media (max-width: 767.98px) { + .adl-multiselect-filter .adlp-filter-header { + display: flex; + } +} +.adl-multiselect-filter .adlp-filter-header span { + font-size: 20px; + font-weight: 600; +} +.adl-multiselect-filter .adlp-filter-header .adlp-close-button { + width: 24px; + height: 24px; + color: #394962; +} +.adl-multiselect-filter .adlp-filter-header .adlp-close-button:before { + font-family: "adl-icons"; + content: "\e90a"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +.adl-multiselect-filter .adlp-dropdown-data { + position: relative; + padding: 16px; + border-radius: 6px 6px 0 0; + background-color: #fff; +} +.adl-multiselect-filter .adlp-selected-data { + display: block; +} +.adl-multiselect-filter .adlp-selected-data-item { + font-size: 15px; + color: #00112c; + font-weight: 300; +} +.adl-multiselect-filter .adlp-selected-data-item:first-child { + margin-top: 16px; +} +.adl-multiselect-filter .adlp-selected-data-item .adlt--remove-item { + margin: 4px 8px 4px 0; +} +.adl-multiselect-filter .adlp-selected-data-item .adlt--remove-item:before { + font-family: "adl-icons"; + content: "\e903"; + line-height: 1; + font-weight: 500; + font-size: 9px; + display: flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + border-radius: 50%; + border: 1px solid #dce0e5; + color: #394962; +} +.adl-multiselect-filter .adlp-data-label { + display: block; + margin-bottom: 8px; + font-size: 15px; + font-weight: 600; + color: #00112c; +} +.adl-multiselect-filter .adlp-buttons { + position: relative; + display: flex; + justify-content: space-between; + padding: 16px; + background: #f3f6f9; + border-top: 1px solid #dce0e5; + border-radius: 0 0 6px 6px; +} +.adl-multiselect-filter .adlp-buttons .adl-button { + font-size: 13px; +} + +body { + color: #000; + background-color: #f3f6f9; +} + +table { + width: 100%; + border-collapse: collapse; +} +table thead tr th { + border-bottom: 1px solid #dce0e5; +} +table tr:not(:last-of-type) td { + border-bottom: 1px solid #dce0e5; +} +table tr th, table tr td { + text-align: center; + padding: 14px; +} +table tr th.adlm--left-aligned, table tr td.adlm--left-aligned { + text-align: left; +} +table tr th.adlm--blue-text, table tr td.adlm--blue-text { + color: #0066ff; +} +table tr td { + font-weight: 300; +} +table tr td:last-of-type { + white-space: nowrap; +} +table tr th { + font-weight: 600; +} + +ul.adl-bullet-list { + list-style: disc; + padding-left: 15px; + margin-top: 16px; +} +ul.adl-bullet-list li { + padding-left: 5px; +} + +@font-face { + font-family: "FaktPro"; + font-style: normal; + font-weight: 300; + src: local("Fakt Blond"), url(../assets/fonts/FaktPro/FaktPro-Blond.woff) format("woff"); +} +@font-face { + font-family: "FaktPro"; + font-style: italic; + font-weight: 300; + src: local("FaktPro Blond Italic"), url(../assets/fonts/FaktPro/FaktPro-BlondItalic.woff) format("woff"); +} +@font-face { + font-family: "FaktPro"; + font-style: normal; + font-weight: 400; + src: local("FaktPro Normal"), url(../assets/fonts/FaktPro/FaktPro-Normal.woff) format("woff"); +} +@font-face { + font-family: "FaktPro"; + font-style: italic; + font-weight: 400; + src: local("FaktPro Normal Italic"), url(../assets/fonts/FaktPro/FaktPro-NormalItalic.woff) format("woff"); +} +@font-face { + font-family: "FaktPro"; + font-style: normal; + font-weight: 600; + src: local("FaktPro SemiBold"), url(../assets/fonts/FaktPro/FaktPro-SemiBold.woff) format("woff"); +} +/** + * Font face declaration for icons + */ +@font-face { + font-family: "adl-icons"; + src: url("../assets/fonts/adl-icons/adl-icons.eot"); + src: url("../assets/fonts/adl-icons/adl-icons.eot") format("embedded-opentype"), url("../assets/fonts/adl-icons/adl-icons.ttf") format("truetype"), url("../assets/fonts/adl-icons/adl-icons.woff") format("woff"), url("../assets/fonts/adl-icons/adl-icons.svg") format("svg"); + font-weight: normal; + font-style: normal; +} +#adl-page { + /* + description: 'Remove default browser button styles.' + */ + /* + description: + 'Fix placeholder styles (https://css-tricks.com/almanac/selectors/p/placeholder/).', + '(default placeholder opacity)' + */ +} +#adl-page button, #adl-page a { + cursor: pointer; +} +#adl-page button { + background: transparent; + border: 0; + padding: 0; + font: inherit; + color: inherit; + text-decoration: inherit; + text-transform: inherit; +} +#adl-page a { + text-decoration: none; + color: inherit; +} +#adl-page input, #adl-page textarea, #adl-page select, #adl-page button, #adl-page a { + font-size: inherit; + font-family: inherit; + line-height: inherit; + font-weight: inherit; + text-decoration: inherit; + text-transform: inherit; +} +#adl-page button, #adl-page a, #adl-page ul, #adl-page li, #adl-page div, #adl-page tr, #adl-page input, #adl-page textarea { + outline: none; + box-sizing: border-box; +} +#adl-page button:focus, #adl-page button:hover, #adl-page a:focus, #adl-page a:hover, #adl-page ul:focus, #adl-page ul:hover, #adl-page li:focus, #adl-page li:hover, #adl-page div:focus, #adl-page div:hover, #adl-page tr:focus, #adl-page tr:hover, #adl-page input:focus, #adl-page input:hover, #adl-page textarea:focus, #adl-page textarea:hover { + outline: none; +} +#adl-page ul { + list-style-type: none; + margin: 0; + padding: 0; + padding-inline-start: 0; +} +#adl-page ul > li { + margin: 0; + padding: 0; +} +#adl-page input::placeholder { + opacity: 1; +} +#adl-page input::-ms-input-placeholder { + opacity: 1; +} +#adl-page input:-ms-input-placeholder { + opacity: 1; +} +#adl-page div, #adl-page p, #adl-page pre, #adl-page table, +#adl-page form, #adl-page fieldset, +#adl-page main, #adl-page header, #adl-page footer, #adl-page nav, #adl-page section, +#adl-page ul, #adl-page li, #adl-page ol, #adl-page dl, #adl-page dt, #adl-page dd, +#adl-page h1, #adl-page h2, #adl-page h3, #adl-page h4, #adl-page h5, #adl-page h6, +#adl-page hr, +#adl-page article, #adl-page aside, #adl-page details, #adl-page dialog, #adl-page figcaption, #adl-page figure { + box-sizing: border-box; + font: inherit; + font-family: "FaktPro", sans-serif; + font-size: 16px; + line-height: 1.4; +} +#adl-page h1, #adl-page h2, #adl-page h3, #adl-page h4, #adl-page h5, #adl-page h6, #adl-page p { + margin: 0; + padding: 0; + font-weight: 400; + text-decoration: inherit; + text-transform: inherit; +} +#adl-page h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.33; + margin: 12px 0; +} +#adl-page p a { + font-weight: normal; +} +#adl-page button[disabled] { + pointer-events: none; +} +#adl-page button[disabled] > * { + pointer-events: none; +} +#adl-page *, #adl-page *:before, #adl-page *:after { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: geometricPrecision; +} +#adl-page .adl-button { + position: relative; + display: inline-block; + padding: 10px 16px; + font-weight: 600; + font-size: 15px; + cursor: pointer; + transition: all 0.2s; + border-radius: 6px; +} +#adl-page .adl-button:focus { + box-shadow: 0 0 0 3px #cce0ff; +} +#adl-page .adl-button span { + display: flex; + align-items: center; + column-gap: 8px; +} +#adl-page .adl-button span:before, #adl-page .adl-button span:after { + font-family: "adl-icons"; + line-height: 1; + font-weight: 500; + font-size: inherit; +} +#adl-page .adl-button.adlm--full-width { + width: 100%; +} +#adl-page .adl-button.adlm--small { + padding: 4px 8px; +} +#adl-page .adl-button.adlm--medium { + padding: 6px 16px; +} +#adl-page .adl-button.adlt--primary { + background-color: #0066ff; + color: #fff; +} +#adl-page .adl-button.adlt--primary.adlm--destructive { + background-color: #e50000; +} +#adl-page .adl-button.adlt--primary.adls--disabled, #adl-page .adl-button.adlt--primary:disabled { + background-color: #dce0e5; + color: #a5afbd; + pointer-events: none; +} +#adl-page .adl-button.adlt--primary:hover, #adl-page .adl-button.adlt--primary:focus { + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); +} +#adl-page .adl-button.adlt--secondary { + border: 1px solid #dce0e5; + background-color: #fff; + color: #394962; +} +#adl-page .adl-button.adlt--secondary:hover { + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); +} +#adl-page .adl-button.adlt--secondary.adlm--destructive { + color: #e50000; +} +#adl-page .adl-button.adlt--secondary.adlm--blue { + color: #0066ff; +} +#adl-page .adl-button.adlt--secondary.adls--disabled, #adl-page .adl-button.adlt--secondary:disabled { + color: #a5afbd; + pointer-events: none; +} +#adl-page .adl-button.adlt--ghost { + border: none; + background-color: transparent; + color: #394962; +} +#adl-page .adl-button.adlt--ghost:hover, #adl-page .adl-button.adlt--ghost:focus, #adl-page .adl-button.adlt--ghost:active { + background-color: #fff; +} +#adl-page .adl-button.adlt--ghost.adlm--blue { + color: #0066ff; +} +#adl-page .adl-button.adlt--ghost.adlm--destructive { + color: #e50000; +} +#adl-page .adl-button.adlt--ghost.adls--disabled, #adl-page .adl-button.adlt--ghost:disabled { + background-color: #dce0e5; + color: #a5afbd; + pointer-events: none; +} +#adl-page .adl-button.adlm--icon-only { + padding: 10px; +} +#adl-page .adl-button.adlm--icon-only span { + display: none; +} +#adl-page .adl-payments-page .adl-payment-methods-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + column-gap: 20px; + margin-top: 20px; +} +#adl-page .adl-payments-page .adl-payment-methods-header h2 { + margin: 0 0 18px; +} +#adl-page .adl-payments-page .adl-payment-methods-header .adlp-add-methods-button span { + white-space: nowrap; +} +@media (max-width: 767.98px) { + #adl-page .adl-payments-page .adl-payment-methods-header .adlp-add-methods-button { + position: fixed; + margin: auto; + bottom: 12px; + left: 50%; + width: calc(100% - 24px); + transform: translateX(-50%); + z-index: 1; + } + #adl-page .adl-payments-page .adl-payment-methods-header .adlp-add-methods-button span { + display: block; + text-align: center; + } +} +#adl-page .adl-payments-page .adlp-payment-logo { + max-width: 20px; + max-height: 20px; + margin-right: 8px; +} +@media (max-width: 1280.98px) { + #adl-page .adl-payments-page .adlp-payment-logo { + max-width: 40px; + max-height: 26px; + margin-right: 16px; + } +} +#adl-page .adl-payments-page .adlp-status-header { + display: flex; + gap: 4px; +} +#adl-page .adl-payments-page .adlp-status-header .adl-hint { + width: 14px; + height: 18px; + color: #0075ff; +} +#adl-page .adl-payments-page .adlp-status-header .adl-hint:before { + font-family: "adl-icons"; + content: "\e910"; + line-height: 1; + font-weight: 500; + font-size: 10px; +} +#adl-page .adl-payments-page .adlp-status-header .adl-hint .adlp-tooltip { + font-weight: 300; + max-width: 230px; + text-align: left; +} +#adl-page .adl-payments-page .adlp-configure-method-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; +} +#adl-page .adl-payments-page .adlp-configure-method-header h2 { + display: flex; + align-items: flex-start; + gap: 20px; + margin-bottom: 18px; +} +#adl-page .adl-payments-page .adlp-configure-method-header .adlp-status-badge { + padding: 4px 8px; + color: #08A648; + border: solid 1px #cef2dd; + background-color: #E6F8ED; + border-radius: 4px; + font-size: 12px; +} +#adl-page .adl-payments-page .adlp-configure-method-header .adlp-status-badge.adlt--inactive { + color: #720000; + border: solid 1px #e50000; + background-color: #facccc; +} +#adl-page .adl-payments-page .adlp-configure-method-header .adlp-payment-logo { + max-width: 50px; + max-height: 25px; + margin: 0; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper { + margin-top: 24px; + position: relative; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-table-filters { + display: flex; + flex-direction: row; + margin-top: 24px; + column-gap: 8px; + flex-wrap: wrap; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-reset-button:hover { + background-color: #dce0e5; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-reset-button span:before { + font-family: "adl-icons"; + content: "\e90e"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-filters-switch-button { + display: none; +} +#adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-filters-switch-button span:before { + font-family: "adl-icons"; + content: "\e912"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +@media (max-width: 767.98px) { + #adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-table-filters { + display: none; + column-gap: 24px; + row-gap: 18px; + } + #adl-page .adl-payments-page .adlp-table-filter-wrapper.adls--filters-active .adlp-table-filters { + display: flex; + } + #adl-page .adl-payments-page .adlp-table-filter-wrapper.adls--filters-active .adlp-filters-switch-button { + background-color: #dce0e5; + } + #adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-filters-switch-button { + display: block; + } + #adl-page .adl-payments-page .adlp-table-filter-wrapper .adlp-reset-button { + position: absolute; + top: 0; + right: 0; + } +} +#adl-page .adl-payments-page .adlp-separator { + margin-top: 16px; + width: 100%; + height: 6px; + background-color: #f3f6f9; +} +#adl-page .adl-payments-page .adl-file-drop-zone { + margin-top: 20px; +} +@media (max-width: 1280.98px) { + #adl-page .adl-payments-page .adlp-info-status .adlp-info-label .adl-hint { + display: none; + } +} +#adl-page .adl-payments-page .adlm--inline { + position: relative; +} +#adl-page .adl-single-select-dropdown { + position: relative; + border-radius: 6px; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button { + width: 100%; + padding: 8px 16px; + display: flex; + justify-content: space-between; + column-gap: 30px; + align-items: center; + border-radius: 6px; + border: 1px solid #dce0e5; + background-color: #fff; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button:after { + font-family: "adl-icons"; + content: "\e90d"; + line-height: 1; + font-weight: 500; + font-size: 11px; + color: #8390a3; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button:focus { + border: 1px solid #599bff; + box-shadow: 0 0 0 3px #cce0ff; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button span { + font-weight: 300; + display: flex; + width: 100%; + justify-content: space-between; + color: #69778c; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button span.adls--selected { + color: #00112c; +} +@media (max-width: 767.98px) { + #adl-page .adl-single-select-dropdown .adlp-dropdown-button span.adls--selected { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-button input { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + border-radius: 6px 6px 0 0; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list { + display: none; + position: absolute; + width: 100%; + scrollbar-width: none; + max-height: 180px; + overflow-y: auto; + flex-direction: column; + gap: 0; + border-top: none; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + background-color: #fff; + color: #00112c; + box-shadow: 2px 2px 2px -1px rgba(0, 0, 0, 0.25); + z-index: 5; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list.adls--show { + display: flex; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list::-webkit-scrollbar { + display: none; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list-item { + position: relative; + display: flex; + align-items: center; + padding: 8px 16px; + font-size: 15px; + font-weight: 300; + line-height: 1.6; + color: #00112c; + cursor: pointer; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list-item:hover { + background-color: #f3f6f9; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list-item.adls--selected { + position: relative; +} +#adl-page .adl-single-select-dropdown .adlp-dropdown-list-item.adls--selected:after { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 11px; + position: absolute; + right: 16px; + color: #394962; + font-weight: 700; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border: 1px solid #599bff; + border-bottom: 1px solid #e3e6eb; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(-10px -10px -1px -10px); +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active { + box-shadow: none; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active + .adlp-dropdown-list { + box-shadow: none; + border: 1px solid #dce0e5; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active input { + display: block; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results { + border-radius: 6px; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results input { + border-radius: 6px; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results + .adlp-dropdown-list { + display: none; +} +#adl-page .adl-single-select-dropdown.adls--active .adlp-dropdown-list { + border: 1px solid #599bff; + border-top: none; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(0 -10px -10px -10px); +} +#adl-page .adl-single-select-dropdown.adls--disabled .adlp-dropdown-button { + background-color: #f3f6f9; + color: #69778c; + border: 1px solid #dce0e5; + pointer-events: none; +} +#adl-page .adl-single-select-dropdown.adls--error .adlp-dropdown-button { + border: 1px solid #ee5959; +} +#adl-page .adl-single-select-dropdown.adlm--inline .adlp-dropdown-list { + position: relative; +} +#adl-page .adl-multiselect-dropdown { + position: relative; + border-radius: 6px; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button { + width: 100%; + padding: 8px 16px; + display: flex; + justify-content: space-between; + column-gap: 30px; + align-items: center; + border-radius: 6px; + border: 1px solid #dce0e5; + background-color: #fff; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button:after { + font-family: "adl-icons"; + content: "\e90d"; + line-height: 1; + font-weight: 500; + font-size: 11px; + color: #8390a3; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button:focus { + border: 1px solid #599bff; + box-shadow: 0 0 0 3px #cce0ff; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button span { + font-weight: 300; + display: flex; + width: 100%; + justify-content: space-between; + color: #69778c; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button span.adls--selected { + color: #00112c; +} +@media (max-width: 767.98px) { + #adl-page .adl-multiselect-dropdown .adlp-dropdown-button span.adls--selected { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-button input { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + border-radius: 6px 6px 0 0; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list { + display: none; + position: absolute; + width: 100%; + scrollbar-width: none; + max-height: 180px; + overflow-y: auto; + flex-direction: column; + gap: 0; + border-top: none; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + background-color: #fff; + color: #00112c; + box-shadow: 2px 2px 2px -1px rgba(0, 0, 0, 0.25); + z-index: 5; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list.adls--show { + display: flex; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list::-webkit-scrollbar { + display: none; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item { + position: relative; + display: flex; + align-items: center; + padding: 8px 16px; + font-size: 15px; + font-weight: 300; + line-height: 1.6; + color: #00112c; + cursor: pointer; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item:hover { + background-color: #f3f6f9; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--selected { + position: relative; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--selected:after { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 11px; + position: absolute; + right: 16px; + color: #394962; + font-weight: 700; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border: 1px solid #599bff; + border-bottom: 1px solid #e3e6eb; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(-10px -10px -1px -10px); +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active { + box-shadow: none; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active + .adlp-dropdown-list { + box-shadow: none; + border: 1px solid #dce0e5; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active input { + display: block; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results { + border-radius: 6px; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results input { + border-radius: 6px; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-button.adls--search-active.adls--no-results + .adlp-dropdown-list { + display: none; +} +#adl-page .adl-multiselect-dropdown.adls--active .adlp-dropdown-list { + border: 1px solid #599bff; + border-top: none; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(0 -10px -10px -10px); +} +#adl-page .adl-multiselect-dropdown.adls--disabled .adlp-dropdown-button { + background-color: #f3f6f9; + color: #69778c; + border: 1px solid #dce0e5; + pointer-events: none; +} +#adl-page .adl-multiselect-dropdown.adls--error .adlp-dropdown-button { + border: 1px solid #ee5959; +} +#adl-page .adl-multiselect-dropdown.adlm--inline .adlp-dropdown-list { + position: relative; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item:before { + content: ""; + -webkit-appearance: none; + background: #fff; + border: 1px solid #a5afbd; + border-radius: 4px; + padding: 7px; + display: inline-block; + position: relative; + cursor: pointer; + margin-right: 12px; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--selected:before { + background-color: #0066ff; + border: 1px solid #0066ff; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--selected:after { + right: unset; + left: 19px; + color: #fff; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--disabled { + pointer-events: none; + color: #8390a3; +} +#adl-page .adl-multiselect-dropdown .adlp-dropdown-list-item.adls--disabled:before { + background: #dce0e5; +} +#adl-page .adl-multiselect-dropdown .adlp-checkbox { + display: none; +} +#adl-page .adl-multiselect-filter { + position: relative; + width: fit-content; +} +#adl-page .adl-multiselect-filter .adlp-filter-button { + padding: 0 16px; + height: 32px; + display: inline-flex; + align-items: center; + font-weight: 400; +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-tooltip { + position: absolute; + top: calc(-100% - 3px); + white-space: nowrap; + background-color: #20304c; + color: #fff; + font-weight: 600; + left: 50%; + transform: translateX(-50%); + font-size: 13px; + padding: 4px 8px; + border-radius: 4px; + display: none; +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-tooltip:after { + content: ""; + display: block; + position: absolute; + width: 10px; + height: 10px; + background: #00112c; + transform: rotate(45deg); + border-radius: 2px; + bottom: -5px; + left: calc(50% - 5px); +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button { + display: none; + border-radius: 50%; + width: 24px; + height: 24px; +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:after { + font-family: "adl-icons"; + content: "\e90a"; + line-height: 1; + font-weight: 500; + font-size: 9px; + font-weight: 600; + color: #fff; +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:hover { + background: #fff; +} +#adl-page .adl-multiselect-filter .adlp-filter-button .adlp-delete-text-button:hover:after { + color: #20304c; +} +#adl-page .adl-multiselect-filter .adlp-filter-button.adls--selected { + display: inline-flex; + align-items: center; + padding: 0 8px 0 16px; + gap: 8px; + background-color: #20304c; + color: #fff; + border-color: transparent; + font-weight: 600; +} +#adl-page .adl-multiselect-filter .adlp-filter-button.adls--selected:hover .adlp-tooltip { + display: block; +} +#adl-page .adl-multiselect-filter .adlp-filter-button.adls--selected .adlp-delete-text-button { + display: inline-block; +} +#adl-page .adl-multiselect-filter .adl-single-select-dropdown .adlp-dropdown-list-item.adls--selected:after { + display: none; +} +#adl-page .adl-multiselect-filter .adlp-dropdown-container { + display: none; + position: absolute; + top: 48px; + z-index: 1; +} +#adl-page .adl-multiselect-filter .adlp-dropdown-container .adlp-content { + width: 350px; + border-radius: 6px; + border: 1px solid #dce0e5; + box-shadow: 0 8px 16px rgba(0, 17, 44, 0.1); + background-color: #fff; +} +#adl-page .adl-multiselect-filter .adlp-dropdown-container.adls--open { + display: block; +} +@media (max-width: 767.98px) { + #adl-page .adl-multiselect-filter .adlp-dropdown-container.adls--open { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 17, 44, 0.4); + } +} +#adl-page .adl-multiselect-filter .adlp-filter-header { + position: relative; + display: none; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 16px; +} +@media (max-width: 767.98px) { + #adl-page .adl-multiselect-filter .adlp-filter-header { + display: flex; + } +} +#adl-page .adl-multiselect-filter .adlp-filter-header span { + font-size: 20px; + font-weight: 600; +} +#adl-page .adl-multiselect-filter .adlp-filter-header .adlp-close-button { + width: 24px; + height: 24px; + color: #394962; +} +#adl-page .adl-multiselect-filter .adlp-filter-header .adlp-close-button:before { + font-family: "adl-icons"; + content: "\e90a"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page .adl-multiselect-filter .adlp-dropdown-data { + position: relative; + padding: 16px; + border-radius: 6px 6px 0 0; + background-color: #fff; +} +#adl-page .adl-multiselect-filter .adlp-selected-data { + display: block; +} +#adl-page .adl-multiselect-filter .adlp-selected-data-item { + font-size: 15px; + color: #00112c; + font-weight: 300; +} +#adl-page .adl-multiselect-filter .adlp-selected-data-item:first-child { + margin-top: 16px; +} +#adl-page .adl-multiselect-filter .adlp-selected-data-item .adlt--remove-item { + margin: 4px 8px 4px 0; +} +#adl-page .adl-multiselect-filter .adlp-selected-data-item .adlt--remove-item:before { + font-family: "adl-icons"; + content: "\e903"; + line-height: 1; + font-weight: 500; + font-size: 9px; + display: flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + border-radius: 50%; + border: 1px solid #dce0e5; + color: #394962; +} +#adl-page .adl-multiselect-filter .adlp-data-label { + display: block; + margin-bottom: 8px; + font-size: 15px; + font-weight: 600; + color: #00112c; +} +#adl-page .adl-multiselect-filter .adlp-buttons { + position: relative; + display: flex; + justify-content: space-between; + padding: 16px; + background: #f3f6f9; + border-top: 1px solid #dce0e5; + border-radius: 0 0 6px 6px; +} +#adl-page .adl-multiselect-filter .adlp-buttons .adl-button { + font-size: 13px; +} +#adl-page .adl-store-switcher { + position: relative; + width: 100%; + display: flex; + align-items: center; + gap: 12px; + padding: 0; + background-color: #fff; +} +@media (min-width: 768px) { + #adl-page .adl-store-switcher { + max-width: 200px; + } +} +#adl-page .adl-store-switcher .adlp-stores { + display: none; +} +#adl-page .adl-store-switcher .adlp-stores.adls--show { + display: flex; + flex-direction: column; + gap: 0; + min-width: 190px; + top: 57px; + left: -12px; + max-height: 123px; + overflow-y: auto; + scrollbar-width: none; + position: absolute; + border-radius: 8px; + border: 1px solid #dce0e5; + background-color: #fff; + color: #00112c; + z-index: 5; +} +#adl-page .adl-store-switcher .adlp-stores.adls--show .adlp-store { + cursor: pointer; +} +#adl-page .adl-store-switcher .adlp-stores.adls--show .adlp-store:hover { + background-color: #f3f6f9; +} +#adl-page .adl-store-switcher .adlp-store { + font-weight: 300; + padding: 8px 16px; + color: #00112c; + font-size: 15px; + line-height: 1.6; +} +#adl-page .adl-store-switcher .adlp-store.adls--selected { + display: flex; + justify-content: space-between; + align-items: center; + column-gap: 10px; +} +#adl-page .adl-store-switcher .adlp-store.adls--selected:after { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 11px; + font-weight: 600; + color: #394962; +} +#adl-page .adl-store-switcher .adl-store-icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 32px; + height: 32px; + border-radius: 6px; + background-color: #20304c; + color: #fff; +} +#adl-page .adl-store-switcher .adl-store-icon:before { + font-family: "adl-icons"; + content: "\e915"; + line-height: 1; + font-weight: 500; + font-size: 14px; +} +#adl-page .adl-store-switcher .adlp-switch-text { + height: fit-content; + padding: 0; + font-weight: 300; + font-size: 13px; + line-height: 1.3; + color: #69778c; + text-transform: none; + border-bottom: none; +} +#adl-page .adl-store-switcher .adlp-switch-store-button { + font-weight: 600; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + font-size: 15px; + line-height: 1.4; + color: #20304c; +} +@media (min-width: 768px) { + #adl-page .adl-store-switcher .adlp-switch-store-button { + max-width: 123px; + } + #adl-page .adl-store-switcher .adlp-switch-store-button span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +#adl-page .adl-store-switcher .adlp-switch-store-button span { + text-align: left; +} +#adl-page .adl-store-switcher .adlp-switch-store-button:after { + font-family: "adl-icons"; + content: "\e90d"; + line-height: 1; + font-weight: 500; + font-size: 7px; + color: #00112c; +} +#adl-page #adl-main-header .adl-header-navigation { + width: 100%; + max-width: fit-content; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid #dce0e5; +} +@media (max-width: 767.98px) { + #adl-page #adl-main-header .adl-header-navigation { + max-width: unset; + padding: 5px 12px; + } +} +#adl-page #adl-main-header .adl-alert { + margin-top: 20px; +} +#adl-page #adl-main-header .adlp-nav-list { + display: flex; + column-gap: 1px; + flex-wrap: wrap; + background-color: #dce0e5; +} +@media (max-width: 767.98px) { + #adl-page #adl-main-header .adlp-nav-list { + flex-direction: column; + } +} +#adl-page #adl-main-header .adlp-nav-item { + background-color: #fff; + display: flex; + align-items: center; + padding: 9px 12px; + gap: 12px; + min-width: 184px; + flex: 1; +} +@media (max-width: 767.98px) { + #adl-page #adl-main-header .adlp-nav-item { + padding-left: 0; + } + #adl-page #adl-main-header .adlp-nav-item:not(:first-child) { + border-left: none; + } +} +#adl-page #adl-main-header .adlp-nav-item-icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 32px; + height: 32px; + border-radius: 6px; + background-color: #20304c; + color: #fff; +} +#adl-page #adl-main-header .adlm--merchant .adlp-nav-item-icon:before { + font-family: "adl-icons"; + content: "\e913"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page #adl-main-header .adlm--mode .adlp-nav-item-icon:before { + font-family: "adl-icons"; + content: "\e900"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page #adl-main-header .adlm--download .adlp-nav-item-icon:before { + font-family: "adl-icons"; + content: "\e914"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page #adl-main-header .adlp-download-link { + align-items: center; +} +#adl-page #adl-main-header .adlp-download-link:after { + display: none; +} +#adl-page #adl-main-header .adlp-download-link:hover span { + text-decoration: none; +} +#adl-page #adl-main-header h3.adlp-nav-item-title { + height: initial; + padding: 0; + font-weight: 300; + font-family: "FaktPro", sans-serif; + font-size: 13px; + border-bottom: none; + color: #69778c; + line-height: 1; + text-transform: none; +} +#adl-page #adl-main-header .adlp-nav-item-subtitle { + font-weight: 600; + font-size: 15px; + line-height: 1.4; + color: #20304c; +} +@media (min-width: 768px) { + #adl-page #adl-main-header .adlp-nav-item-subtitle { + max-width: 123px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +#adl-page #adl-main-header .adl-store-switcher { + position: relative; + width: 100%; + display: flex; + align-items: center; + gap: 12px; + padding: 0; + background-color: #fff; +} +@media (min-width: 768px) { + #adl-page #adl-main-header .adl-store-switcher { + max-width: 200px; + } +} +#adl-page #adl-main-header .adl-store-switcher .adlp-stores { + display: none; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-stores.adls--show { + display: flex; + flex-direction: column; + gap: 0; + min-width: 190px; + top: 57px; + left: -12px; + max-height: 123px; + overflow-y: auto; + scrollbar-width: none; + position: absolute; + border-radius: 8px; + border: 1px solid #dce0e5; + background-color: #fff; + color: #00112c; + z-index: 5; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-stores.adls--show .adlp-store { + cursor: pointer; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-stores.adls--show .adlp-store:hover { + background-color: #f3f6f9; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-store { + font-weight: 300; + padding: 8px 16px; + color: #00112c; + font-size: 15px; + line-height: 1.6; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-store.adls--selected { + display: flex; + justify-content: space-between; + align-items: center; + column-gap: 10px; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-store.adls--selected:after { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 11px; + font-weight: 600; + color: #394962; +} +#adl-page #adl-main-header .adl-store-switcher .adl-store-icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 32px; + height: 32px; + border-radius: 6px; + background-color: #20304c; + color: #fff; +} +#adl-page #adl-main-header .adl-store-switcher .adl-store-icon:before { + font-family: "adl-icons"; + content: "\e915"; + line-height: 1; + font-weight: 500; + font-size: 14px; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-switch-text { + height: fit-content; + padding: 0; + font-weight: 300; + font-size: 13px; + line-height: 1.3; + color: #69778c; + text-transform: none; + border-bottom: none; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-switch-store-button { + font-weight: 600; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + font-size: 15px; + line-height: 1.4; + color: #20304c; +} +@media (min-width: 768px) { + #adl-page #adl-main-header .adl-store-switcher .adlp-switch-store-button { + max-width: 123px; + } + #adl-page #adl-main-header .adl-store-switcher .adlp-switch-store-button span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +#adl-page #adl-main-header .adl-store-switcher .adlp-switch-store-button span { + text-align: left; +} +#adl-page #adl-main-header .adl-store-switcher .adlp-switch-store-button:after { + font-family: "adl-icons"; + content: "\e90d"; + line-height: 1; + font-weight: 500; + font-size: 7px; + color: #00112c; +} +#adl-page a, #adl-page .adl-link { + position: relative; + margin-right: 8px; + color: #0066ff; + font-weight: 300; + font-size: 15px; + text-decoration: none; + transition: color 0.2s; + display: inline-flex; + flex-direction: row; + gap: 8px; +} +#adl-page a:hover span, #adl-page .adl-link:hover span { + text-decoration: underline; +} +#adl-page a[target=_blank]:after, #adl-page .adl-link[target=_blank]:after { + font-family: "adl-icons"; + content: "\e901"; + line-height: 1; + font-weight: 500; + font-size: 7px; + position: relative; + top: 3px; +} +#adl-page a.adlt--underlined span, #adl-page .adl-link.adlt--underlined span { + text-decoration: underline; +} +#adl-page .adl-text-input { + height: initial; + padding: 8px 16px; + font-weight: 300; + font-size: 16px; + font-family: "FaktPro", sans-serif; + color: #000; + background-color: #fff; + border: 1px solid #dce0e5; + border-radius: 6px; +} +#adl-page .adl-text-input:focus, #adl-page .adl-text-input:active { + border-color: #0066ff; +} +#adl-page .adl-text-input.adlm--full-width { + width: 100%; +} +#adl-page .adl-text-input::placeholder { + color: #69778c; +} +#adl-page .adl-text-input::-ms-input-placeholder { + color: #69778c; +} +#adl-page .adl-text-input:-ms-input-placeholder { + color: #69778c; +} +#adl-page .adl-password { + position: relative; + display: flex; + flex-direction: row; + align-items: center; +} +#adl-page .adl-password input { + height: initial; + padding: 8px 16px; + font-weight: 300; + font-size: 16px; + font-family: "FaktPro", sans-serif; + color: #000; + background-color: #fff; + border: 1px solid #dce0e5; + border-radius: 6px; + padding-right: 41px; + width: 100%; +} +#adl-page .adl-password input:focus, #adl-page .adl-password input:active { + border-color: #0066ff; +} +#adl-page .adl-password input.adlm--full-width { + width: 100%; +} +#adl-page .adl-password input::placeholder { + color: #69778c; +} +#adl-page .adl-password input::-ms-input-placeholder { + color: #69778c; +} +#adl-page .adl-password input:-ms-input-placeholder { + color: #69778c; +} +#adl-page .adl-password span { + cursor: pointer; + position: absolute; + right: 15px; + height: 18px; +} +#adl-page .adl-password span:before { + font-family: "adl-icons"; + content: "\e911"; + line-height: 1; + font-weight: 500; + font-size: 16px; +} +#adl-page .adl-password span:hover:before { + font-weight: 600; +} +#adl-page .adl-radio-input span { + font-weight: 300; +} +#adl-page .adl-radio-input [type=radio] { + display: none; +} +#adl-page .adl-radio-input [type=radio] + span { + position: relative; + padding-left: 28px; + cursor: pointer; + line-height: 20px; + display: inline-block; + color: #555; +} +#adl-page .adl-radio-input [type=radio] + span:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 16px; + height: 16px; + box-sizing: border-box; + border: 1px solid #ddd; + border-radius: 100%; + background: #fff; +} +#adl-page .adl-radio-input [type=radio] + span:after { + content: ""; + width: 16px; + height: 16px; + box-sizing: border-box; + border: 4px solid #0066ff; + position: absolute; + top: 0; + left: 0; + border-radius: 100%; + transition: all 0.2s ease; + opacity: 0; + transform: scale(0); +} +#adl-page .adl-radio-input [type=radio]:checked + span:after { + opacity: 1; + transform: scale(1); +} +#adl-page .adl-radio-input-group { + display: flex; + align-items: center; + gap: 20px; +} +#adl-page .adl-loader { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +#adl-page .adl-loader .adlp-spinner { + border: 3px solid rgba(0, 102, 255, 0.1); + border-top-color: #0066ff; + border-radius: 50%; + width: 24px; + height: 24px; + animation: loader-spin 0.8s linear infinite; +} +#adl-page .adl-loader.adlt--small .adlp-spinner { + border-width: 2px; + width: 16px; + height: 16px; +} +#adl-page .adl-loader.adlt--large .adlp-spinner { + border-width: 4px; + width: 48px; + height: 48px; +} +#adl-page .adl-loader.adlm--dark .adlp-spinner { + border-color: rgba(255, 255, 255, 0.1); + border-top-color: #fff; +} +@keyframes loader-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +#adl-page .adl-toggle { + position: relative; + display: inline-block; + width: 34px; + height: 17px; +} +#adl-page .adl-toggle input { + opacity: 0; + width: 0; + height: 0; +} +#adl-page .adl-toggle .adlp-toggle-round { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #a5afbd; + transition: 0.4s; + border-radius: 13px; +} +#adl-page .adl-toggle .adlp-toggle-round:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 2px; + bottom: 2px; + background-color: #fff; + transition: 0.4s; + border-radius: 50%; +} +#adl-page .adl-toggle input:checked + .adlp-toggle-round { + background-color: #0066ff; +} +#adl-page .adl-toggle input:focus + .adlp-toggle-round { + box-shadow: 0 0 1px #0066ff; +} +#adl-page .adl-toggle input:checked + .adlp-toggle-round:before { + transform: translateX(17px); +} +#adl-page .adl-field-wrapper { + width: 100%; + display: flex; + flex-direction: column; + margin: 24px 0; +} +#adl-page .adl-field-wrapper .adlp-field-subtitle { + font-size: 16px; +} +#adl-page .adl-field-wrapper h3.adlp-field-title { + font-weight: 600; + text-transform: none; + font-size: 18px; + height: initial; + padding: 0; + margin-bottom: 0; + border-bottom: none; + color: #00112c; + background-color: transparent; +} +#adl-page .adl-field-wrapper .adlp-field-subtitle { + display: block; + margin-bottom: 4px; + color: #20304c; +} +#adl-page .adl-field-wrapper .adlp-field-component:invalid { + border: 1px solid #e50000; + color: #e50000; +} +#adl-page .adl-field-wrapper .adlp-field-component:invalid + .adlp-input-error { + display: block; +} +#adl-page .adl-field-wrapper .adlp-input-error { + display: none; + margin-top: 4px; + font-size: 13px; + color: #e50000; +} +#adl-page .adl-field-wrapper.adlt--checkbox .adlp-field-title { + display: flex; + align-items: center; + justify-content: space-between; + column-gap: 10px; +} +#adl-page .adl-field-wrapper.adlt--checkbox .adlp-field-subtitle { + padding-right: 40px; +} +#adl-page .adl-field-wrapper.adls--error .adlp-field-component { + border: 1px solid #e50000; + color: #e50000; +} +#adl-page .adl-field-wrapper.adls--error .adlp-input-error { + display: block; +} +#adl-page .adl-field-wrapper.adlm--turned .adls--active .adlp-dropdown-button { + border-radius: 0 0 6px 6px; + border: 1px solid #599bff; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(-10px -10px -10px -10px); +} +#adl-page .adl-field-wrapper.adlm--turned .adls--active .adlp-dropdown-button:after { + transform: rotate(180deg); +} +#adl-page .adl-field-wrapper.adlm--turned .adls--active .adlp-dropdown-list { + bottom: 40px; + border-radius: 6px 6px 0 0; + border: 1px solid #599bff; + border-bottom: 1px solid #e3e6eb; + box-shadow: 0 0 0 3px #cce0ff; + clip-path: inset(-10px -10px 0 -10px); +} +#adl-page .adl-table-wrapper { + margin: 24px 0; + padding: 24px; + border: 1px solid #dce0e5; + border-radius: 6px; + font-size: 13px; +} +@media (max-width: 1280.98px) { + #adl-page .adl-table-wrapper { + display: none; + } +} +#adl-page .adl-table-wrapper table tr .adl-button { + font-size: inherit; + display: inline-flex; + align-items: center; + column-gap: 8px; +} +#adl-page .adl-table-wrapper table tr .adlt--add-button:before { + font-family: "adl-icons"; + content: "\e90f"; + line-height: 1; + font-weight: 500; + font-size: inherit; +} +#adl-page .adl-table-wrapper table tr .adlt--edit-button:before { + font-family: "adl-icons"; + content: "\e904"; + line-height: 1; + font-weight: 500; + font-size: inherit; +} +#adl-page .adl-table-wrapper table tr .adlt--edit-button span { + display: none; +} +#adl-page .adl-table-wrapper table tr .adlt--delete-button:before { + font-family: "adl-icons"; + content: "\e90c"; + line-height: 1; + font-weight: 500; + font-size: inherit; +} +@media (max-width: 1280.98px) { + #adl-page .adl-table-wrapper table tr .adlt--delete-button { + display: none; + } +} +#adl-page .adl-table-wrapper table tr .adlt--delete-button span { + display: none; +} +#adl-page .adl-table-wrapper table th { + text-align: center; +} +#adl-page .adl-table-wrapper table th.adlm--left-aligned { + text-align: left; +} +@media (max-width: 1280.98px) { + #adl-page .adl-notifications-table .adl-table-wrapper { + display: block; + } +} +#adl-page .adl-toaster { + width: fit-content; + max-width: 420px; + display: flex; + justify-content: space-between; + padding: 12px 9px 12px 16px; + border-radius: 8px; + background-color: #00112c; + position: fixed; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + gap: 20px; +} +#adl-page .adl-toaster.adls--closed { + display: none; +} +#adl-page .adl-toaster .adlp-toaster-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + font-size: 15px; + color: #fff; + line-height: 21px; +} +#adl-page .adl-toaster .adlp-toaster-title:before { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 9px; + width: 18px; + height: 18px; + background-color: #60d58f; + border-radius: 50%; + display: inline-flex; + justify-content: center; + align-items: center; + flex-shrink: 0; +} +#adl-page .adl-toaster .adl-button { + padding: 7px; +} +#adl-page .adl-toaster .adl-button span:before { + font-family: "adl-icons"; + content: "\e90b"; + line-height: 1; + font-weight: 500; + font-size: 7px; + color: #394962; +} +#adl-page .adl-alert { + width: 100%; + display: flex; + justify-content: space-between; + padding: 24px 19px 24px 24px; + border-radius: 4px; +} +#adl-page .adl-alert.adls--closed { + display: none; +} +#adl-page .adl-alert .adlp-alert-title { + display: flex; + align-items: flex-start; + font-size: 15px; + line-height: 21px; +} +#adl-page .adl-alert .adlp-alert-title:before { + position: relative; + top: 3px; +} +#adl-page .adl-alert .adlp-message { + display: flex; + flex-direction: column; +} +#adl-page .adl-alert .adlp-message .adlp-message-title { + font-weight: 600; +} +#adl-page .adl-alert .adl-button { + padding: 7px; +} +#adl-page .adl-alert .adl-button span:before { + font-family: "adl-icons"; + content: "\e90b"; + line-height: 1; + font-weight: 500; + font-size: 7px; + color: inherit; +} +#adl-page .adl-alert.adlt--success { + background-color: #cef2dd; + color: #055f29; +} +#adl-page .adl-alert.adlt--success .adlp-alert-title { + gap: 18px; +} +#adl-page .adl-alert.adlt--success .adlp-alert-title:before { + font-family: "adl-icons"; + content: "\e91a"; + line-height: 1; + font-weight: 500; + font-size: 11px; + top: 5px; +} +#adl-page .adl-alert.adlt--warning { + background-color: #ffeacc; + color: #7F4A00; +} +#adl-page .adl-alert.adlt--warning .adlp-alert-title { + gap: 17px; +} +#adl-page .adl-alert.adlt--warning .adlp-alert-title:before { + font-family: "adl-icons"; + content: "\e919"; + line-height: 1; + font-weight: 500; + font-size: 13px; +} +#adl-page .adl-alert.adlt--error { + background-color: #facccc; + color: #720000; +} +#adl-page .adl-alert.adlt--error .adlp-alert-title { + gap: 16px; +} +#adl-page .adl-alert.adlt--error .adlp-alert-title:before { + font-family: "adl-icons"; + content: "\e908"; + line-height: 1; + font-weight: 500; + font-size: 15px; +} +#adl-page .adl-data-table-wrapper { + display: none; + width: 100%; + margin: 24px 0; +} +@media (max-width: 1280.98px) { + #adl-page .adl-data-table-wrapper { + display: flex; + flex-direction: column; + row-gap: 24px; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card { + position: relative; + display: flex; + flex-direction: column; + padding: 0 14px 16px; + border: 1px solid #dce0e5; + border-radius: 4px; +} +@media (max-width: 767.98px) { + #adl-page .adl-data-table-wrapper .adlp-data-card { + padding: 40px 6px 16px; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-method-name { + max-width: calc(100% - 220px); + padding: 27px 0; + display: flex; + align-items: center; + font-size: 22px; + line-height: 1.45; + color: #000; +} +@media (max-width: 767.98px) { + #adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-method-name { + padding: 0 10px 27px; + max-width: 100%; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-info-item { + flex: 1; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions { + position: absolute; + right: 14px; + top: 20px; + display: flex; + align-items: center; + justify-content: flex-end; + column-gap: 9px; +} +@media (max-width: 767.98px) { + #adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions { + right: unset; + top: unset; + padding: 0 10px; + position: relative; + justify-content: center; + column-gap: 16px; + flex-direction: row-reverse; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adl-button { + font-size: inherit; + display: flex; + align-items: center; + justify-content: center; + column-gap: 8px; + padding: 10px 16px; + border: 1px solid #dce0e5; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adl-button:not(.adlm--destructive) { + color: #0066ff; +} +@media (max-width: 1280.98px) { + #adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adl-button { + width: 100%; + } + #adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adl-button span { + justify-content: center; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adlt--add-button span:before { + content: "\e90f"; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adlt--edit-button span:before { + content: "\e904"; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-actions .adlt--delete-button span:before { + content: "\e90c"; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-info-list { + display: flex; + column-gap: 32px; + row-gap: 32px; + padding-top: 16px; + border-top: 1px solid #dce0e5; +} +@media (max-width: 767.98px) { + #adl-page .adl-data-table-wrapper .adlp-data-card .adlp-payment-info-list { + display: grid; + padding: 26px 20px 0; + margin-bottom: 32px; + grid-template-areas: "info-status info-currencies" "info-countries info-type"; + } +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-info-label { + font-weight: 600; + display: block; + margin-bottom: 15px; + font-size: 15px; + line-height: 21px; + color: #00112c; + text-transform: uppercase; +} +#adl-page .adl-data-table-wrapper .adlp-data-card .adlp-info-data { + font-weight: 300; + font-size: 18px; + line-height: 1.17; + color: #00112c; +} +#adl-page .adl-modal { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(0, 17, 44, 0.4); + z-index: 100; + display: flex; + align-items: center; + justify-content: center; +} +@media (max-width: 767.98px) { + #adl-page .adl-modal { + padding: 10px; + } +} +#adl-page .adl-modal .adlp-modal-content { + background: #fff; + display: flex; + max-height: 90%; + max-width: 90%; + flex-direction: column; + align-items: center; + position: relative; + margin: 0 auto; + box-shadow: 0 8px 8px rgba(0, 17, 44, 0.04), 0 2px 4px rgba(0, 17, 44, 0.08); + border-radius: 12px; +} +#adl-page .adl-modal .adlp-modal-content .adlp-close-button { + position: absolute; + width: 24px; + height: 24px; + top: 15px; + right: 12px; + display: flex; + align-items: center; + justify-content: center; + color: #394962; + z-index: 1; +} +#adl-page .adl-modal .adlp-modal-content .adlp-close-button span:before { + content: "\e90a"; +} +#adl-page .adl-modal .adlp-modal-content .adlp-title { + color: #00112c; + font-size: 20px; + font-weight: 600; + text-align: left; + padding: 16px 40px 16px 24px; + width: 100%; +} +#adl-page .adl-modal .adlp-modal-content .adlp-body { + padding: 0 24px; + align-items: flex-start; + justify-content: flex-start; + width: 100%; + overflow: hidden auto; + font-size: 15px; + font-weight: 300; +} +#adl-page .adl-modal .adlp-modal-content .adlp-body.adlm--full-width { + padding: 20px 0; +} +#adl-page .adl-modal .adlp-modal-content .adlp-footer { + padding: 24px; + display: flex; + flex-direction: row; + justify-content: flex-end; + width: 100%; + flex-shrink: 0; + gap: 10px; +} +#adl-page .adls--hidden { + display: none !important; +} +#adl-page .adl-page-loader { + position: fixed; + z-index: 500; + background-color: rgba(0, 17, 44, 0.4); + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +#adl-page .adlp-status { + display: flex; + align-items: center; + gap: 6px; +} +#adl-page .adlp-status:before { + content: ""; + width: 6px; + height: 6px; + display: block; + border-radius: 50%; +} +#adl-page .adlp-status.adlt--active:before { + background: #0ABF53; +} +#adl-page .adlp-status.adlt--inactive:before { + background: #e50000; +} +#adl-page .adlp-status.adlt--warning:before { + background: #ff9900; +} +#adl-page .adlp-status.adlt--error:before { + background: #ff3d00; +} +#adl-page .adlp-status.adlt--info:before { + background: #00a3ff; +} +#adl-page .adlp-status.adlt--created:before { + background: #00a3ff; +} +#adl-page .adlp-status.adlt--queued:before { + background: #b5b5b5; +} +#adl-page .adlp-status.adlt--in_progress:before { + background: #00a3ff; +} +#adl-page .adlp-status.adlt--completed:before { + background: #0a7001; +} +#adl-page .adlp-status.adlt--aborted:before { + background: #ff3d00; +} +#adl-page .adlp-status.adlt--failed:before { + background: #ff3d00; +} +#adl-page * + .adlp-flash-message-wrapper, #adl-page .adlp-flash-message-wrapper + * { + margin-top: 20px; +} + +body { + background-color: white; + box-sizing: border-box; + display: flex; +} diff --git a/tests/Integration/.gitkeep b/Resources/views/backend/_resources/css/adyen.css similarity index 100% rename from tests/Integration/.gitkeep rename to Resources/views/backend/_resources/css/adyen.css diff --git a/Resources/views/backend/_resources/favicon.ico b/Resources/views/backend/_resources/favicon.ico new file mode 100644 index 00000000..0dc0000a Binary files /dev/null and b/Resources/views/backend/_resources/favicon.ico differ diff --git a/Resources/views/backend/_resources/images/adyen-logo.png b/Resources/views/backend/_resources/images/adyen-logo.png new file mode 100644 index 00000000..ea0aa365 Binary files /dev/null and b/Resources/views/backend/_resources/images/adyen-logo.png differ diff --git a/Resources/views/backend/_resources/js/AdyenShopNotifications.js b/Resources/views/backend/_resources/js/AdyenShopNotifications.js new file mode 100644 index 00000000..920435bc --- /dev/null +++ b/Resources/views/backend/_resources/js/AdyenShopNotifications.js @@ -0,0 +1,53 @@ +// {block name="backend/index/view/menu" append} +Ext.onReady(function () { + Ext.define('Shopware.apps.AdyenPayment.ShopNotifications', function () { + return { + statics: { + openAdyenModule: function (storeId, page) { + let pageSuffix = page ? '#' + page : ''; + + sessionStorage.setItem('adl-active-store-id', storeId); + Shopware.ModuleManager.createSimplifiedModule( + "AdyenPaymentMain" + pageSuffix, + { + "title": "Adyen", + maximized: true + } + ); + } + } + }; + }); + + Ext.Ajax.request({ + method: 'POST', + url: "{url controller=AdyenShopNotifications action=get}", + async: true, + success: function (response) { + var result = JSON.parse(response.responseText); + + result.forEach( + function (item) { + Shopware.Notification.createStickyGrowlMessage({ + title: Ext.String.format( + "{s name='notification/adyen/header'}You have new Adyen notifications ({literal}{0}{/literal}){/s}", + item.storeName + ), + text: "{s name='notification/adyen/message'}Open the Adyen plugin dashboard page to view them{/s}", + btnDetail: { + text: "{s name='notification/adyen/open'}Open{/s}", + callback: function () { + Shopware.apps.AdyenPayment.ShopNotifications.openAdyenModule( + item.storeId, 'notifications-shop' + ); + } + }, + onCloseButton: function () { + } + }) + } + ) + } + }); +}); +//{/block} diff --git a/Resources/views/backend/_resources/js/AjaxService.js b/Resources/views/backend/_resources/js/AjaxService.js new file mode 100644 index 00000000..c501e3b0 --- /dev/null +++ b/Resources/views/backend/_resources/js/AjaxService.js @@ -0,0 +1,177 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +/** + * @typedef AjaxServiceType + * @property {(url:string, errorCallback?: (error?: Record) => Promise | any, fallthrough?: + * boolean) => Promise} get + * @property {(url:string, data?: any, customHeader?: Record, errorCallback: (error?: + * Record) => Promise) => Promise} post + * @property {(url:string, data?: any, customHeader?: Record, errorCallback: (error?: + * Record) => Promise) => Promise} put + * @property {(url:string, data?: any, errorCallback?: (error?: Record) => Promise) => + * Promise} delete + */ +(function () { + /** + * Ajax/API service. + * + * @returns {AjaxServiceType} + */ + const AjaxService = () => { + let callValidationState = ''; + + /** + * Handles the server response. + * @param {Response} response + * @param {(error: Record) => Promise?} errorCallback + * @returns {Record} + */ + const handleResponse = (response, errorCallback) => { + if (!errorCallback) { + errorCallback = AdyenFE.responseService.errorHandler; + } + + try { + if (response.ok) { + return response.json(); + } + + if (response.status === 401) { + // reset the state so all requests should become obsolete + callValidationState = Math.random().toString(36); + + return response.json().then(AdyenFE.responseService.unauthorizedHandler).catch(errorCallback); + } + + if (response.status === 400) { + return response.json().then(errorCallback); + } + } catch (e) {} + + return errorCallback({ status: response.status, error: response.statusMessage }); + }; + + /** + * Performs GET ajax request. + * + * @param {string} url The URL to call. + * @param {(error: Record) => Promise?} errorCallback + * @param {boolean?} [fallthrough=false] + * @returns {Promise} + */ + const get = (url, errorCallback, fallthrough = false) => + call({ + method: 'GET', + url, + errorCallback, + fallthrough + }); + + /** + * Performs POST ajax request. + * + * @param {string} url The URL to call. + * @param {Record?} data + * @param {Record?} customHeader + * @param {(error: Record) => Promise?} errorCallback + */ + const post = (url, data, customHeader, errorCallback) => + call({ + method: 'POST', + url, + data, + errorCallback, + customHeader + }); + + /** + * Performs PUT ajax request. + * + * @param {string} url The URL to call. + * @param {Record} data + * @param {Record?} customHeader + * @param {(error: Record) => Promise?} errorCallback + */ + const put = (url, data, customHeader, errorCallback) => + call({ + method: 'PUT', + url, + data, + errorCallback, + customHeader + }); + + /** + * Performs DELETE ajax request. + * + * @param {string} url The URL to call. + * @param {Record?} data + * @param {(error: Record) => Promise?} errorCallback + */ + const del = (url, data, errorCallback) => + call({ + method: 'DELETE', + url, + data, + errorCallback + }); + + /** + * Performs ajax call. + * + * @param {'GET' | 'POST' | 'PUT' | 'DELETE'} method The HTTP method. + * @param {string} url The URL to call. + * @param {Record?} data The data to send. + * @param {(error: Record) => Promise?} errorCallback An error callback. If not set, the + * default one will be used. + * @param {Record?} customHeader + * @param {boolean} fallthrough Indicates whether the request should not be cancelled on generic cancel call. + * + * @returns {Promise>} + */ + const call = ({ method, url, data, errorCallback, customHeader, fallthrough = false }) => { + const callState = callValidationState; + + return new Promise((resolve, reject) => { + url = url.replace('https:', ''); + url = url.replace('http:', ''); + + const headers = { + 'Content-Type': 'application/json', + ...(customHeader || {}) + }; + + if (headers['Content-Type'] === 'multipart/form-data') { + delete headers['Content-Type']; + } + + const body = data + ? headers['Content-Type'] === 'application/json' + ? JSON.stringify(data) + : data + : undefined; + + fetch(url, { method, headers, body }).then((response) => { + if (!fallthrough && callState !== callValidationState) { + // Obsolete request. Some call cancelled all other requests. + console.debug('cancelling an obsolete request', url); + reject({ errorCode: 0 }); + } else { + handleResponse(response, errorCallback).then(resolve).catch(reject); + } + }); + }); + }; + + return { + get, + post, + put, + delete: del + }; + }; + + AdyenFE.ajaxService = AjaxService(); +})(); diff --git a/Resources/views/backend/_resources/js/ConnectionController.js b/Resources/views/backend/_resources/js/ConnectionController.js new file mode 100644 index 00000000..3444bf89 --- /dev/null +++ b/Resources/views/backend/_resources/js/ConnectionController.js @@ -0,0 +1,490 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef ConnectionInfo + * @property {string} apiKey + * @property {string} merchantId + */ + + /** + * @typedef Connection + * @property {'test' | 'live'} mode + * @property {ConnectionInfo?} testData + * @property {ConnectionInfo?} liveData + */ + + /** + * @typedef ConnectionSettings + * @property {'test' | 'live'} mode + * @property {string} apiKey + * @property {string} merchantId + * @property {{label: string, value: string}[]?} merchants + */ + /** + * Handles connection page logic. + * + * @param {{getSettingsUrl: string, submitUrl: string, disconnectUrl: string, getMerchantsUrl: string, + * validateConnectionUrl: string, validateWebhookUrl: string, page: string}} configuration + * @constructor + */ + function ConnectionController(configuration) { + /** @type AjaxServiceType */ + const api = AdyenFE.ajaxService; + + const { + templateService, + elementGenerator: generator, + validationService: validator, + components, + state, + utilities + } = AdyenFE; + /** @type {HTMLElement} */ + let form; + let currentStoreId; + /** @type {boolean} */ + let merchantPage; + /** @type {boolean} */ + let isConnectionSet; + /** @type {ConnectionSettings} */ + let activeSettings; + /** @type {ConnectionSettings} */ + let changedSettings; + /** @type {number} */ + let numberOfChanges = 0; + + /** + * Displays page content. + * + * @param {{ state?: string, storeId: string }} config + */ + this.display = ({ storeId }) => { + utilities.showLoader(); + currentStoreId = storeId; + merchantPage = configuration.page === 'merchant'; + templateService.clearMainPage(); + + configuration.getSettingsUrl = configuration.getSettingsUrl.replace('{storeId}', storeId); + configuration.getMerchantsUrl = configuration.getMerchantsUrl.replace('{storeId}', currentStoreId); + configuration.submitUrl = configuration.submitUrl.replace('{storeId}', storeId); + configuration.validateConnectionUrl = configuration.validateConnectionUrl.replace('{storeId}', storeId); + configuration.validateWebhookUrl = configuration.validateWebhookUrl.replace('{storeId}', storeId); + configuration.disconnectUrl = configuration.disconnectUrl.replace('{storeId}', storeId); + + state + .getCurrentMerchantState() + .then((state) => { + isConnectionSet = state === 'dashboard'; + return api.get(configuration.getSettingsUrl, () => null).then(createForm); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Sets the unsaved changes. + * + * @return {boolean} + */ + this.hasUnsavedChanges = () => false; + + /** + * Renders the form. + * + * @param {ConnectionSettings} data + */ + const renderForm = (data) => { + form = generator.createElement('form'); + + const components = [ + generator.createRadioGroupField({ + name: 'mode', + value: data.mode || 'test', + label: 'connection.environment.title', + description: 'connection.environment.description', + options: [ + { label: 'connection.environment.options.test', value: 'test' }, + { label: 'connection.environment.options.live', value: 'live' } + ], + onChange: (value) => handleChange('mode', value) + }), + generator.createPasswordField({ + name: 'apiKey', + value: data.apiKey, + label: 'connection.apiKey.title', + description: 'connection.apiKey.description', + error: 'connection.apiKey.error', + onChange: (value) => handleChange('apiKey', value) + }), + generator.createDropdownField({ + name: 'merchantId', + value: data.merchantId, + placeholder: 'connection.merchant.placeholder', + options: data.merchants, + label: 'connection.merchant.title', + description: 'connection.merchant.description', + error: 'connection.merchant.error', + onChange: (value) => handleChange('merchantId', value) + }) + ]; + + if (merchantPage) { + components[0].classList.add('adls--hidden'); + components[1].classList.add('adls--hidden'); + } else { + components[2].classList.add('adls--hidden'); + } + + form.append( + generator.createElement('div', 'adlp-flash-message-wrapper'), + generator.createElement('h2', '', `connection.title${merchantPage ? '_merchant' : ''}`), + generator.createElement( + 'p', + 'adlp-merchant-account-description', + `connection.subtitle${merchantPage ? '_merchant' : ''}` + ), + ...components + ); + + if (isConnectionSet) { + if (!merchantPage) { + form.append( + generator.createButton({ + type: 'secondary', + name: 'validateButton', + disabled: !data.apiKey, + label: 'connection.validateCredentials', + onClick: handleValidateCredentials + }) + ); + } + + form.append( + generator.createFormFooter( + handleFormSubmit, + () => { + this.display({ storeId: currentStoreId }); + }, + 'general.discardChanges', + !merchantPage + ? [ + generator.createButton({ + type: 'secondary', + name: 'disconnectButton', + label: 'connection.disconnect', + className: 'adlm--destructive', + onClick: showDisconnectModal + }) + ] + : [] + ) + ); + } else { + form.append( + generator.createButton({ + type: 'primary', + name: 'saveButton', + disabled: !data.apiKey, + label: merchantPage ? 'connection.next' : 'connection.connect', + onClick: handleFormSubmit + }) + ); + } + + templateService.clearMainPage(); + templateService.getMainPage().append(form); + }; + + /** + * Creates the form. + * + * @param {Connection?} settings + */ + const createForm = (settings) => { + const mode = settings?.mode || 'test'; + /** @type ConnectionSettings */ + const data = { mode: mode, apiKey: '', merchantId: '', merchants: [] }; + if (settings?.[`${mode}Data`]) { + data.apiKey = settings[`${mode}Data`].apiKey; + data.merchantId = settings[`${mode}Data`].merchantId; + } + + changedSettings = utilities.cloneObject(data); + activeSettings = utilities.cloneObject(data); + + if (data.apiKey) { + document + .querySelector('.adl-sidebar [href="#connection-merchant"]') + .parentElement.classList.remove('adls--disabled'); + } + + if (merchantPage) { + if (!data.apiKey) { + state.goToState('connection'); + return Promise.resolve(); + } + + return api + .get(configuration.getMerchantsUrl, () => []) + .then( + /** @param {Merchant[]} response */ + (response) => { + data.merchants = response.map((merchant) => ({ + value: merchant.merchantId, + label: merchant.merchantName + })); + + renderForm(data); + } + ); + } else { + renderForm(data); + + return Promise.resolve(); + } + }; + + /** + * + * @param {keyof ConnectionSettings} prop + * @param {any} value + */ + const handleChange = (prop, value) => { + changedSettings[prop] = value; + if (prop === 'mode') { + changedSettings.apiKey = ''; + form['apiKey'].value = ''; + } else { + validator.validateRequiredField(form['apiKey'], 'connection.apiKey.error'); + } + + if (isConnectionSet) { + renderFooterState(); + } else { + form['saveButton'].disabled = !form['apiKey'].value; + } + }; + + /** + * Converts form data to the settings object. + * + * @return {Connection} + */ + const getFormData = () => ({ + mode: changedSettings.mode, + [changedSettings.mode + 'Data']: { + apiKey: changedSettings.apiKey, + merchantId: changedSettings.merchantId || null + } + }); + + /** + * Saves the connection configuration. + * + * @returns {boolean} + */ + const handleFormSubmit = () => { + const isValid = + validator.validateRequiredField(form['mode']) && + validator.validateRequiredField(form['apiKey'], 'connection.apiKey.error') && + (!merchantPage || validator.validateRequiredField(form['merchantId'])); + + if (isValid) { + if (isConnectionSet && activeSettings.mode !== changedSettings.mode) { + performChangeEnvironmentSteps(); + + return false; + } + + utilities.showLoader(); + api.post(configuration.submitUrl, getFormData()) + .then(handleSaveSuccess) + .finally(() => { + utilities.hideLoader(); + }); + } + + return false; + }; + + const performChangeEnvironmentSteps = () => { + showConfirmModal('changeEnvironment').then((confirmed) => { + if (!confirmed) { + return; + } + + utilities.showLoader(); + changedSettings.merchantId = ''; + + return api + .post(configuration.validateConnectionUrl, getFormData()) + .then((response) => { + if (!response.status) { + showFlashMessage(response.errorCode, 'error'); + return false; + } + + return true; + }) + .then((next) => next && api.delete(configuration.disconnectUrl).then(() => true)) + .then( + (next) => + next && + api.post(configuration.submitUrl, getFormData()).then(() => { + state.goToState('connection-merchant'); + }) + ) + .finally(() => { + utilities.hideLoader(); + }); + }); + }; + + const handleSaveSuccess = () => { + const finishSave = () => { + activeSettings = { ...changedSettings }; + renderFooterState(); + showFlashMessage('connection.messages.connectionUpdated', 'success'); + }; + + utilities.remove401Message(); + if (merchantPage) { + if (!isConnectionSet) { + state.enableSidebar(); + state.goToState('payments'); + state.setHeader(); + } else { + finishSave(); + } + } else if (!isConnectionSet) { + state.goToState('connection-merchant'); + } else { + finishSave(); + } + }; + + /** + * Validates credentials. + * + * @return {Promise} + */ + const handleValidateCredentials = () => { + utilities.showLoader(); + return Promise.all([ + api + .post(configuration.validateConnectionUrl, getFormData(), () => false) + .then((response) => response.status), + api.post(configuration.validateWebhookUrl).then((response) => response.status) + ]) + .then(([t1, t2]) => { + if (t1 && t2) { + showFlashMessage('connection.messages.validCredentials', 'success'); + } else if (!t1) { + showFlashMessage('connection.errors.credentialsValidationError', 'error'); + } else { + showFlashMessage('connection.errors.webhookValidationError', 'error'); + } + + return t1 && t2; + }) + .catch(() => false) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Shows the disconnect confirmation modal. + */ + const showDisconnectModal = () => { + showConfirmModal('disconnect').then((confirmed) => confirmed && handleDisconnect()); + }; + + /** + * Shows the confirmation modal dialog. + * + * @param {string} type + * @returns {Promise} + */ + const showConfirmModal = (type) => { + return new Promise((resolve) => { + const modal = components.Modal.create({ + title: `connection.${type}Modal.title`, + className: `adl-confirm-modal`, + content: [generator.createElement('p', '', `connection.${type}Modal.message`)], + footer: true, + buttons: [ + { + type: 'secondary', + label: 'general.cancel', + onClick: () => { + modal.close(); + resolve(false); + } + }, + { + type: 'primary', + className: 'adlm--destructive', + label: 'general.confirm', + onClick: () => { + modal.close(); + resolve(true); + } + } + ] + }); + + modal.open(); + }); + }; + + const handleDisconnect = () => { + utilities.showLoader(); + api.delete(configuration.disconnectUrl) + .then(() => { + window.location.reload(); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Displays the flash message. + * + * @param {string} message Translation key or message + * @param {'success' | 'error'} status + */ + const showFlashMessage = (message, status = 'success') => { + const container = form?.querySelector('.adlp-flash-message-wrapper'); + if (!container) { + return; + } + + templateService.clearComponent(container); + container.append(utilities.createFlashMessage(message, status)); + container.scrollIntoView({ behavior: 'smooth' }); + }; + + /** + * Handles footer visibility state. + */ + const renderFooterState = () => { + numberOfChanges = 0; + + Object.entries(changedSettings).forEach(([prop, value]) => { + if (prop !== 'merchants' && activeSettings[prop] !== value) { + numberOfChanges++; + } + }); + + utilities.renderFooterState(numberOfChanges); + }; + } + + AdyenFE.ConnectionController = ConnectionController; +})(); diff --git a/Resources/views/backend/_resources/js/DataTableComponent.js b/Resources/views/backend/_resources/js/DataTableComponent.js new file mode 100644 index 00000000..676e0740 --- /dev/null +++ b/Resources/views/backend/_resources/js/DataTableComponent.js @@ -0,0 +1,168 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +if (!window.AdyenFE.components) { + window.AdyenFE.components = {}; +} + +(function () { + const { elementGenerator: generator, translationService } = AdyenFE; + /** + * @typedef TableCell + * @property {string?} label + * @property {string?} className + * @property {(cell: HTMLTableCellElement) => void?} renderer + */ + + /** + * Renders table cell. + * + * @param {'td' | 'th'} type + * @param {TableCell} cellData + * @returns {HTMLElement} + */ + const renderCell = (type, cellData) => { + const cell = generator.createElement(type, cellData.className, cellData.label); + cellData.renderer && cellData.renderer(cell); + + return cell; + }; + + const getTableElement = () => { + return generator.createElementFromHTML( + '
' + ); + }; + + /** + * Data table component. + * + * @param {TableCell[]} headers + * @param {TableCell[][]} items + * @param {string?} className + */ + const createDataTable = (headers, items, className) => { + const tableWrapper = getTableElement(); + + headers.forEach((header) => { + header.label = translationService.translate(header.label); + }); + + const heading = tableWrapper.querySelector('thead tr'); + heading.append(...headers.map((cellData) => renderCell('th', cellData))); + + createTableRows(tableWrapper, items); + + return generator.createElement('div', className, '', null, [tableWrapper]); + }; + + /** + * Creates a data table. + * + * @param {TableCell[]} header + * @param {TableCell[][]} items + * @return {HTMLElement} + */ + const createPaymentsDataTable = (header, items) => { + /** + * Renders item card data. + * + * @param {TableCell} cellHeader + * @param {TableCell} cellData + * @param {string} className + * @returns {HTMLElement} + */ + const createCardItemElements = (cellHeader, cellData, className) => { + const header = generator.createElement('span', 'adlp-info-label', cellHeader.label); + cellHeader.renderer && cellHeader.renderer(header); + const cell = generator.createElement('span', 'adlp-info-data', cellData.label); + cellData.renderer && cellData.renderer(cell); + + return generator.createElement('li', `adlp-payment-info-item ${className}`, '', null, [header, cell]); + }; + + const tableWrapper = getTableElement(); + const mobileWrapper = generator.createElement('div', 'adl-data-table-wrapper'); + mobileWrapper.append( + ...items.map((item) => { + const paymentName = generator.createElement('div', 'adlp-payment-method-name', item[0].label); + const infoList = generator.createElement('ul', 'adlp-payment-info-list', '', null, [ + createCardItemElements(header[4], item[4], 'adlp-info-status'), + createCardItemElements(header[1], item[1], 'adlp-info-currencies'), + createCardItemElements(header[2], item[2], 'adlp-info-countries'), + createCardItemElements(header[3], item[3], 'adlp-info-type') + ]); + + const paymentActions = generator.createElement('div', 'adlp-payment-actions'); + + item[0].renderer(paymentName); + item[5].renderer(paymentActions); + + return generator.createElement('div', 'adlp-data-card', '', null, [ + paymentName, + infoList, + paymentActions + ]); + }) + ); + + const heading = tableWrapper.querySelector('thead tr'); + heading.append(...header.map((cellData) => renderCell('th', cellData))); + + createTableRows(tableWrapper, items); + + return generator.createElement('div', '', '', null, [tableWrapper, mobileWrapper]); + }; + + /** + * Renders table rows. + * + * @param tableWrapper + * @param {TableCell[][]} items + */ + const createTableRows = (tableWrapper, items) => { + items.length && + tableWrapper.querySelector('tbody').append( + ...items.map((item) => { + const row = generator.createElement('tr'); + row.append(...item.map((cellData) => renderCell('td', cellData))); + + return row; + }) + ); + }; + + /** + * Renders the empty list message with the image. + * + * @param {string} label + * @returns {HTMLElement} + */ + const createNoItemsMessage = (label) => { + return generator.createElement('div', 'adlp-no-items-wrapper', '', null, [ + generator.createElementFromHTML( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + ), + generator.createElement('p', '', label) + ]); + }; + + AdyenFE.components.DataTable = { + createDataTable, + createPaymentsDataTable, + createTableRows, + createNoItemsMessage + }; +})(); diff --git a/Resources/views/backend/_resources/js/DropdownComponent.js b/Resources/views/backend/_resources/js/DropdownComponent.js new file mode 100644 index 00000000..25a8682b --- /dev/null +++ b/Resources/views/backend/_resources/js/DropdownComponent.js @@ -0,0 +1,136 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +if (!window.AdyenFE.components) { + window.AdyenFE.components = {}; +} + +(function () { + /** + * @typedef DropdownComponentModel + * + * @property {Option[]} options + * @property {string?} name + * @property {string?} value + * @property {string?} placeholder + * @property {(value: string) => void?} onChange + * @property {boolean?} updateTextOnChange + * @property {boolean?} searchable + */ + + /** + * Single-select dropdown component. + * + * @param {DropdownComponentModel} props + * + * @constructor + */ + const DropdownComponent = ({ + options, + name, + value = '', + placeholder, + onChange, + updateTextOnChange = true, + searchable = false + }) => { + const { elementGenerator: generator, translationService } = AdyenFE; + const filterItems = (text) => { + const filteredItems = text + ? options.filter((option) => option.label.toLowerCase().includes(text.toLowerCase())) + : options; + + if (filteredItems.length === 0) { + selectButton.classList.add('adls--no-results'); + } else { + selectButton.classList.remove('adls--no-results'); + } + + renderOptions(filteredItems); + }; + + const renderOptions = (options) => { + list.innerHTML = ''; + options.forEach((option) => { + const listItem = generator.createElement( + 'li', + 'adlp-dropdown-list-item' + (option === selectedItem ? ' adls--selected' : ''), + option.label + ); + list.append(listItem); + + listItem.addEventListener('click', () => { + hiddenInput.value = option.value; + updateTextOnChange && (buttonSpan.innerHTML = translationService.translate(option.label)); + list.classList.remove('adls--show'); + list.childNodes.forEach((node) => node.classList.remove('adls--selected')); + listItem.classList.add('adls--selected'); + wrapper.classList.remove('adls--active'); + buttonSpan.classList.add('adls--selected'); + selectButton.classList.remove('adls--search-active'); + onChange && onChange(option.value); + }); + }); + }; + + const hiddenInput = generator.createElement('input', 'adlp-hidden-input', '', { type: 'hidden', name, value }); + const wrapper = generator.createElement('div', 'adl-single-select-dropdown'); + + const selectButton = generator.createElement('button', 'adlp-dropdown-button adlp-field-component', '', { + type: 'button' + }); + const selectedItem = options.find((option) => option.value === value); + const buttonSpan = generator.createElement( + 'span', + selectedItem ? 'adls--selected' : '', + selectedItem ? selectedItem.label : placeholder + ); + selectButton.append(buttonSpan); + + const searchInput = generator.createElement('input', 'adl-text-input', '', { + type: 'text', + placeholder: translationService.translate('general.search') + }); + searchInput.addEventListener('input', (event) => filterItems(event.currentTarget?.value || '')); + if (searchable) { + selectButton.append(searchInput); + } + + const list = generator.createElement('ul', 'adlp-dropdown-list'); + renderOptions(options); + + selectButton.addEventListener('click', () => { + list.classList.toggle('adls--show'); + wrapper.classList.toggle('adls--active'); + if (searchable) { + selectButton.classList.toggle('adls--search-active'); + if (selectButton.classList.contains('adls--search-active')) { + searchInput.focus(); + searchInput.value = ''; + filterItems(''); + } + } + }); + + document.documentElement.addEventListener('click', (event) => { + if (!wrapper.contains(event.target) && event.target !== wrapper) { + list.classList.remove('adls--show'); + wrapper.classList.remove('adls--active'); + selectButton.classList.remove('adls--search-active'); + } + }); + + wrapper.append(hiddenInput, selectButton, list); + + return wrapper; + }; + + AdyenFE.components.Dropdown = { + /** + * @param {DropdownComponentModel} config + * @returns {HTMLElement} + */ + create: (config) => DropdownComponent(config) + }; +})(); diff --git a/Resources/views/backend/_resources/js/ElementGenerator.js b/Resources/views/backend/_resources/js/ElementGenerator.js new file mode 100644 index 00000000..6a3fa344 --- /dev/null +++ b/Resources/views/backend/_resources/js/ElementGenerator.js @@ -0,0 +1,691 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef Option + * @property {string?} label + * @property {any} value + */ + + /** + * @typedef {Object.} ElementProps + * @property {string?} name + * @property {any?} value + * @property {string?} className + * @property {string?} placeholder + * @property {(value: any) => any?} onChange + * @property {string?} label + * @property {string?} description + * @property {string?} error + */ + + /** + * @typedef {ElementProps} FormField + * @property {'text' | 'number' | 'radio' |'dropdown' | 'checkbox' | 'file' | 'multiselect' | 'button' | + * 'buttonLink'} type + */ + + const translationService = AdyenFE.translationService; + + /** + * Prevents default event handling. + * @param {Event} e + */ + const preventDefaults = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + /** + * Creates a generic HTML node element and assigns provided class and inner text. + * + * @param {keyof HTMLElementTagNameMap} type Represents the name of the tag + * @param {string?} className CSS class + * @param {string?} innerHTMLKey Inner text translation key. + * @param {Record?} properties An object of additional properties. + * @param {HTMLElement[]?} children + * @returns {HTMLElement} + */ + const createElement = (type, className, innerHTMLKey, properties, children) => { + const child = document.createElement(type); + className && child.classList.add(...className.trim().split(' ')); + if (innerHTMLKey) { + let params = innerHTMLKey.split('|'); + child.innerHTML = translationService.translate(params[0], params.slice(1)); + } + + if (properties) { + if (properties.dataset) { + Object.assign(child.dataset, properties.dataset); + delete properties.dataset; + } + + Object.assign(child, properties); + if (properties.onChange) { + child.addEventListener('change', properties.onChange, false); + } + + if (properties.onClick) { + child.addEventListener('click', properties.onClick, false); + } + } + + if (children) { + child.append(...children); + } + + return child; + }; + + /** + * Creates an element out of provided HTML markup. + * + * @param {string} html + * @returns {HTMLElement} + */ + const createElementFromHTML = (html) => { + const element = document.createElement('div'); + element.innerHTML = html; + + return element.firstElementChild; + }; + + /** + * Creates a button. + * + * @param {{ label?: string, type?: 'primary' | 'secondary' | 'ghost', size?: 'small' | 'medium', className?: + * string, [key: string]: any, onClick?: () => void}} props + * @return {HTMLButtonElement} + */ + const createButton = ({ type, size, className, onClick, label, ...properties }) => { + const cssClass = ['adl-button']; + type && cssClass.push('adlt--' + type); + size && cssClass.push('adlm--' + size); + className && cssClass.push(className); + + const button = createElement('button', cssClass.join(' '), '', { type: 'button', ...properties }, [ + createElement('span', '', label) + ]); + + onClick && + button.addEventListener( + 'click', + (event) => { + preventDefaults(event); + onClick(); + }, + false + ); + + return button; + }; + + /** + * Creates a link that looks like a button. + * + * @param {{text?: string, className?: string, href: string, useDownload?: boolean, downloadFile?: string}} props + * @return {HTMLLinkElement} + */ + const createButtonLink = ({ text, className = '', href, useDownload, downloadFile }) => { + const link = createElement('a', className, `${text}`, { href: href, target: '_blank' }); + if (useDownload) { + link.setAttribute('download', downloadFile); + } + + return link; + }; + + /** + * Creates an input field wrapper around the provided input element. + * + * @param {HTMLElement} input The input element. + * @param {string?} label Label translation key. + * @param {string?} description Description translation key. + * @param {string?} error Error translation key. + * @return {HTMLDivElement} + */ + const createFieldWrapper = (input, label, description, error) => { + const field = createElement('div', 'adl-field-wrapper'); + if (label) { + field.appendChild(createElement('h3', 'adlp-field-title', label)); + } + + if (description) { + field.appendChild(createElement('span', 'adlp-field-subtitle', description)); + } + + field.appendChild(input); + + if (error) { + field.appendChild(createElement('span', 'adlp-input-error', error)); + } + + return field; + }; + + /** + * Creates store switcher. + * + * @param {{value: string, label: string}[]} options + * @param {string?} name + * @param {string?} title + * @param {string?} value + * @param {(value: string) => Promise?} onBeforeChange + * @param {(value: string) => void?} onChange + * @param {boolean?} updateTextOnChange + * @return {HTMLDivElement} + */ + const createStoreSwitcher = (options, name, title, value, onBeforeChange, onChange, updateTextOnChange = true) => { + const hiddenInput = createElement('input', 'adlp-hidden-input', '', { type: 'hidden', name, value }); + const wrapper = createElement('div', 'adl-store-switcher'); + const storeIcon = createElement('span', 'adl-store-icon'); + const switchContent = createElement('div', 'adlp-switch-content'); + const switchText = createElement('h3', 'adlp-switch-text', title); + const list = createElement('ul', 'adlp-stores'); + const switchButton = createElement('button', 'adlp-switch-store-button adlp-field-component', '', { + type: 'button' + }); + const selectedItem = options.find((option) => option.value === value) || options[0]; + const buttonSpan = createElement('span', '', selectedItem.label); + + switchButton.append(buttonSpan); + const listItems = []; + + const handleOnOptionChange = (listItem, storeId) => { + hiddenInput.value = storeId; + updateTextOnChange && (switchButton.firstElementChild.innerHTML = listItem.innerText); + list.classList.remove('adls--show'); + + listItems.forEach((li) => li.classList.remove('adls--selected')); + listItem.classList.add('adls--selected'); + onChange && onChange(storeId); + }; + + options.forEach((option) => { + const listItem = createElement('li', 'adlp-store', option.label); + listItems.push(listItem); + list.append(listItem); + if (option.value === selectedItem.value) { + listItem.classList.add('adls--selected'); + } + + listItem.addEventListener('click', () => { + if (option.value === hiddenInput.value) { + list.classList.remove('adls--show'); + return; + } + + if (!onBeforeChange) { + handleOnOptionChange(listItem, option.value); + } else { + onBeforeChange(option.value).then((resume) => { + if (resume) { + handleOnOptionChange(listItem, option.value); + } else { + list.classList.remove('adls--show'); + } + }); + } + }); + }); + + switchButton.addEventListener('click', (event) => { + preventDefaults(event); + list.classList.toggle('adls--show'); + }); + + document.documentElement.addEventListener('click', () => { + list.classList.remove('adls--show'); + }); + + switchContent.append(switchText, switchButton); + wrapper.append(hiddenInput, storeIcon, switchContent, list); + + return wrapper; + }; + + /** + * Creates dropdown wrapper around the provided dropdown element. + * + * @param {ElementProps & DropdownComponentModel} props The properties. + * @return {HTMLDivElement} + */ + const createDropdownField = ({ label, description, error, ...dropdownProps }) => { + return createFieldWrapper(AdyenFE.components.Dropdown.create(dropdownProps), label, description, error); + }; + + /** + * Creates dropdown wrapper around the provided dropdown element. + * + * @param {(ElementProps & MultiselectDropdownComponentModel)} props The properties. + * @return {HTMLDivElement} + */ + const createMultiselectDropdownField = ({ label, description, error, ...dropdownProps }) => { + return createFieldWrapper( + AdyenFE.components.MultiselectDropdown.create(dropdownProps), + label, + description, + error + ); + }; + + /** + * Creates a password input field. + * + * @param {ElementProps} props The properties. + * @return {HTMLElement} + */ + const createPasswordField = ({ className = '', label, description, error, onChange, ...rest }) => { + const wrapper = createElement('div', `adl-password ${className}`); + const input = createElement('input', 'adlp-field-component', '', { type: 'password', ...rest }); + const span = createElement('span'); + span.addEventListener('click', () => { + if (input.type === 'password') { + input.type = 'text'; + } else { + input.type = 'password'; + } + }); + onChange && input.addEventListener('change', (event) => onChange(event.currentTarget?.value)); + + wrapper.append(input, span); + + return createFieldWrapper(wrapper, label, description, error); + }; + + /** + * Creates a text input field. + * + * @param {ElementProps & { type?: 'text' | 'number' }} props The properties. + * @return {HTMLElement} + */ + const createTextField = ({ className = '', label, description, error, onChange, ...rest }) => { + /** @type HTMLInputElement */ + const input = createElement('input', `adlp-field-component ${className}`, '', { type: 'text', ...rest }); + onChange && input.addEventListener('change', (event) => onChange(event.currentTarget?.value)); + + return createFieldWrapper(input, label, description, error); + }; + + /** + * Creates a number input field. + * + * @param {ElementProps} props The properties. + * @return {HTMLElement} + */ + const createNumberField = ({ onChange, ...rest }) => { + const handleChange = (value) => onChange(value === '' ? null : Number(value)); + + return createTextField({ type: 'number', step: '0.01', onChange: handleChange, ...rest }); + }; + + /** + * Creates a radio group field. + * + * @param {ElementProps} props The properties. + * @return {HTMLElement} + */ + const createRadioGroupField = ({ name, value, className, options, label, description, error, onChange }) => { + const wrapper = createElement('div', 'adl-radio-input-group'); + options.forEach((option) => { + const label = createElement('label', 'adl-radio-input'); + const props = { type: 'radio', value: option.value, name }; + if (value === option.value) { + props.checked = 'checked'; + } + + label.append(createElement('input', className, '', props), createElement('span', '', option.label)); + wrapper.append(label); + onChange && label.addEventListener('click', () => onChange(option.value)); + }); + + return createFieldWrapper(wrapper, label, description, error); + }; + + /** + * Creates a checkbox field. + * + * @param {ElementProps} props The properties. + * @return {HTMLElement} + */ + const createCheckboxField = ({ className = '', label, description, error, onChange, value, ...rest }) => { + /** @type HTMLInputElement */ + const checkbox = createElement('input', 'adlp-toggle-input', '', { type: 'checkbox', checked: value, ...rest }); + onChange && checkbox.addEventListener('change', () => onChange(checkbox.checked)); + + const field = createElement('div', 'adl-field-wrapper adlt--checkbox', '', null, [ + createElement('h3', 'adlp-field-title', label, null, [ + createElement('label', 'adl-toggle', '', null, [checkbox, createElement('span', 'adlp-toggle-round')]) + ]) + ]); + + if (description) { + field.appendChild(createElement('span', 'adlp-field-subtitle', description)); + } + + if (error) { + field.appendChild(createElement('span', 'adlp-input-error', error)); + } + + return field; + }; + + /** + * Creates a button field. + * + * @param {ElementProps & { onClick?: () => void , buttonType?: string, buttonSize?: string, + * buttonLabel?: string}} props The properties. + * @return {HTMLElement} + */ + const createButtonField = ({ label, description, buttonType, buttonSize, buttonLabel, onClick, error }) => { + const button = createButton({ + type: buttonType, + size: buttonSize, + className: '', + label: translationService.translate(buttonLabel), + onClick: onClick + }); + + return createFieldWrapper(button, label, description, error); + }; + + /** + * Creates a field with a link that looks like a button. + * + * @param {ElementProps & {text: string, href: string}} props + */ + const createButtonLinkField = ({ label, text, description, href, error }) => { + const buttonLink = createButtonLink({ + text: translationService.translate(text), + className: '', + href: href + }); + + return createFieldWrapper(buttonLink, label, description, error); + }; + + /** + * Creates a flash message. + * + * @param {string|string[]} messageKey + * @param {'error' | 'warning' | 'success'} status + * @param {number?} clearAfter Time in ms to remove alert message. + * @return {HTMLElement} + */ + const createFlashMessage = (messageKey, status, clearAfter) => { + const hideHandler = () => { + wrapper.remove(); + }; + const wrapper = createElement('div', `adl-alert adlt--${status}`); + let messageBlock; + if (Array.isArray(messageKey)) { + const [titleKey, descriptionKey] = messageKey; + messageBlock = createElement('div', 'adlp-alert-title', '', null, [ + createElement('span', 'adlp-message', '', null, [ + createElement('span', 'adlp-message-title', titleKey), + createElement('span', 'adlp-message-description', descriptionKey) + ]) + ]); + } else { + messageBlock = createElement('span', 'adlp-alert-title', messageKey); + } + + const button = createButton({ onClick: hideHandler }); + + if (clearAfter) { + setTimeout(hideHandler, clearAfter); + } + + wrapper.append(messageBlock, button); + + return wrapper; + }; + + /** + * Adds a label with a hint. + * + * @param {string} label + * @param {string} hint + * @param {'left' | 'right' | 'top' | 'bottom'} position + * @param {string?} className + * @returns HTMLElement + */ + const createHint = (label, hint, position, className = '') => { + const element = createElement('div', `adl-hint ${className}`, label); + element.append(createElement('span', 'adlp-tooltip adlt--' + position, hint)); + element.addEventListener('mouseenter', () => { + element.classList.add('adls--active'); + }); + element.addEventListener('mouseout', () => { + element.classList.remove('adls--active'); + }); + + return element; + }; + + /** + * Creates a toaster message. + * + * @param {string} label + * @param {number} timeout Clear timeout in ms. + * @returns {HTMLElement} + */ + const createToaster = (label, timeout = 5000) => { + const toaster = createElement('div', 'adl-toaster', '', null, [ + createElement('span', 'adlp-toaster-title', label), + createElement('button', 'adl-button', '', null, [createElement('span')]) + ]); + + toaster.children[1].addEventListener('click', () => toaster.remove()); + + setTimeout(() => toaster.remove(), timeout); + + return toaster; + }; + + /** + * + * @param {ElementProps & { supportedMimeTypes: string[] }} props + * @returns {HTMLDivElement} + */ + const createFileUploadField = ({ + name, + placeholder, + label, + description, + error, + value, + onChange, + supportedMimeTypes + }) => { + const setActive = (e) => { + preventDefaults(e); + wrapper.classList.add('adls--active'); + }; + + const setInactive = (e) => { + preventDefaults(e); + wrapper.classList.remove('adls--active'); + }; + + const previewFile = (file, img) => { + let reader = new FileReader(); + reader.readAsDataURL(file); + reader.onloadend = function () { + img.src = reader.result; + }; + }; + + const handleDrop = (e) => { + const file = e.dataTransfer?.files?.[0] || null; + if (file) { + handleFileChange(file); + } + }; + + const handleFileChange = (file) => { + if (!supportedMimeTypes.includes(file.type)) { + AdyenFE.validationService.setError(wrapper, 'validation.invalidImageType'); + return; + } + + if (file.size > 10000000) { + AdyenFE.validationService.setError(wrapper, 'validation.invalidImageSize'); + return; + } + + onChange(file); + AdyenFE.validationService.removeError(wrapper); + textElem.classList.remove('adls--empty'); + textElem.innerText = file.name; + const img = createElement('img'); + textElem.prepend(img); + previewFile(file, img); + }; + + const wrapper = createElement('div', 'adl-file-drop-zone adlp-field-component'); + const labelElem = createElement('label', 'adlp-input-file-label'); + const textElem = createElement('span', 'adlp-file-label' + (!value ? ' adls--empty' : ''), placeholder); + if (value) { + textElem.prepend(createElement('img', '', '', { src: value })); + } + + const fileUpload = createElement('input', 'adlp-input-file', '', { + type: 'file', + accept: 'image/*', + name: name + }); + fileUpload.addEventListener('change', () => handleFileChange(fileUpload.files?.[0])); + + labelElem.append(textElem, fileUpload); + wrapper.append(labelElem); + + ['dragenter', 'dragover'].forEach((eventName) => { + wrapper.addEventListener(eventName, setActive, false); + }); + ['dragleave', 'drop'].forEach((eventName) => { + wrapper.addEventListener(eventName, setInactive, false); + }); + wrapper.addEventListener('drop', handleDrop, false); + + return createFieldWrapper(wrapper, label, description, error); + }; + + /** + * Adds a form footer with save and cancel buttons. + * + * @param {() => void} onSave + * @param {() => void} onCancel + * @param {string} cancelLabel + * @param {HTMLButtonElement[]} extraButtons + * @returns HTMLElement + */ + const createFormFooter = (onSave, onCancel, cancelLabel = 'general.cancel', extraButtons = []) => { + return createElement('div', 'adl-form-footer', '', null, [ + createElement('span', 'adlp-changes-count', 'general.unsavedChanges'), + createElement('div', 'adlp-actions', '', null, [ + ...extraButtons, + createButton({ + type: 'secondary', + className: 'adlp-cancel', + label: cancelLabel, + onClick: onCancel, + disabled: true + }), + createButton({ + type: 'primary', + className: 'adlp-save', + label: 'general.saveChanges', + onClick: onSave, + disabled: true + }) + ]) + ]); + }; + + /** + * Creates form fields based on the fields configurations. + * + * @param {FormField[]} fields + */ + const createFormFields = (fields) => { + /** @type HTMLElement[] */ + const result = []; + fields.forEach(({ type, ...rest }) => { + switch (type) { + case 'text': + result.push(createTextField({ ...rest, className: 'adl-text-input' })); + break; + case 'number': + result.push(createNumberField({ ...rest, className: 'adl-text-input' })); + break; + case 'dropdown': + result.push(createDropdownField(rest)); + break; + case 'multiselect': + result.push(createMultiselectDropdownField(rest)); + break; + case 'radio': + result.push(createRadioGroupField(rest)); + break; + case 'checkbox': + result.push(createCheckboxField(rest)); + break; + case 'file': + result.push(createFileUploadField(rest)); + break; + case 'button': + result.push(createButtonField(rest)); + break; + case 'buttonLink': + result.push(createButtonLinkField(rest)); + break; + } + + rest.className && result[result.length - 1].classList.add(...rest.className.trim().split(' ')); + }); + + return result; + }; + + /** + * Creates a main header item. + * + * @param {string} title + * @param {string} text + * @returns {[HTMLElement,HTMLElement]} + */ + const createHeaderItem = (title, text) => { + return [ + createElement('span', 'adlp-nav-item-icon', ''), + createElement('div', 'adlp-nav-item-text', '', null, [ + createElement('h3', 'adlp-nav-item-title', title), + createElement('span', 'adlp-nav-item-subtitle', text) + ]) + ]; + }; + + AdyenFE.elementGenerator = { + createElement, + createElementFromHTML, + createButton, + createHint, + createDropdownField, + createMultiselectDropdownField, + createPasswordField, + createTextField, + createNumberField, + createRadioGroupField, + createFlashMessage, + createStoreSwitcher, + createFileUploadField, + createButtonField, + createButtonLinkField, + createFormFields, + createFormFooter, + createToaster, + createHeaderItem + }; +})(); diff --git a/Resources/views/backend/_resources/js/ModalComponent.js b/Resources/views/backend/_resources/js/ModalComponent.js new file mode 100644 index 00000000..75cea8ef --- /dev/null +++ b/Resources/views/backend/_resources/js/ModalComponent.js @@ -0,0 +1,139 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +if (!window.AdyenFE.components) { + window.AdyenFE.components = {}; +} + +(function () { + /** + * @typedef ButtonConfig + * @property {string} label + * @property {string?} className + * @property {'primary' | 'secondary'} type + * @property {() => void} onClick + */ + + /** + * @typedef ModalConfiguration + * @property {string?} title + * @property {string?} className + * @property {HTMLElement} content The content of the body. + * @property {ButtonConfig[]} buttons Footer buttons. + * @property {(modal: HTMLDivElement) => void?} onOpen Will fire after the modal is opened. + * @property {() => boolean?} onClose Will fire before the modal is closed. + * If the return value is false, the modal will not be closed. + * @property {boolean} [footer=false] Indicates whether to use footer. Defaults to false. + * @property {boolean} [canClose=true] Indicates whether to use an (X) button or click outside the modal + * to close it. Defaults to true. + * @property {boolean} [fullWidthBody=false] Indicates whether to make body full width + */ + + /** + * @param {ModalConfiguration} configuration + * @constructor + */ + function ModalComponent(configuration) { + const { templateService, translationService, utilities, elementGenerator } = AdyenFE, + config = configuration; + + /** + * @type {HTMLDivElement} + */ + let modal; + + /** + * Closes the modal on Esc key. + * + * @param {KeyboardEvent} event + */ + const closeOnEsc = (event) => { + if (event.key === 'Escape') { + this.close(); + } + }; + + /** + * Closes the modal. + */ + this.close = () => { + if (!config.onClose || config.onClose()) { + window.removeEventListener('keyup', closeOnEsc); + modal?.remove(); + } + }; + + /** + * Opens the modal. + */ + this.open = () => { + const modalTemplate = + '
\n' + + '
' + + ' ' + + '
' + + '
' + + ' ' + + '
' + + '
'; + + modal = AdyenFE.elementGenerator.createElementFromHTML(modalTemplate); + const closeBtn = modal.querySelector('.adlp-close-button'), + closeBtnSpan = modal.querySelector('.adlp-close-button span'), + title = modal.querySelector('.adlp-title'), + body = modal.querySelector('.adlp-body'), + footer = modal.querySelector('.adlp-footer'); + + utilities.showElement(modal); + if (config.canClose === false) { + utilities.hideElement(closeBtn); + } else { + window.addEventListener('keyup', closeOnEsc); + closeBtn.addEventListener('click', this.close); + closeBtnSpan.style.display = 'flex'; + modal.addEventListener('click', (event) => { + if (event.target.id === 'adl-modal') { + event.preventDefault(); + this.close(); + + return false; + } + }); + } + + if (config.title) { + title.innerHTML = translationService.translate(config.title); + } else { + utilities.hideElement(title); + } + + if (config.className) { + modal.classList.add(config.className); + } + + body.append(...(Array.isArray(config.content) ? config.content : [config.content])); + if (configuration.fullWidthBody) { + body.classList.add('adlm--full-width'); + } + + if (config.footer === false || !config.buttons) { + utilities.hideElement(footer); + } else { + config.buttons.forEach((button) => { + footer.appendChild(elementGenerator.createButton(button)); + }); + } + + templateService.getMainPage().parentNode.appendChild(modal); + if (config.onOpen) { + config.onOpen(modal); + } + }; + } + + AdyenFE.components.Modal = { + /** @param {ModalConfiguration} config */ + create: (config) => new ModalComponent(config) + }; +})(); diff --git a/Resources/views/backend/_resources/js/MultiselectDropdownComponent.js b/Resources/views/backend/_resources/js/MultiselectDropdownComponent.js new file mode 100644 index 00000000..ebc81a82 --- /dev/null +++ b/Resources/views/backend/_resources/js/MultiselectDropdownComponent.js @@ -0,0 +1,179 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +if (!window.AdyenFE.components) { + window.AdyenFE.components = {}; +} + +(function () { + const preventDefaults = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + /** + * @typedef MultiselectDropdownComponentModel + * + * @property {Option[]} options + * @property {string?} name + * @property {string[]?} values + * @property {string?} placeholder + * @property {string?} selectedText + * @property {(values: string[]) => void} onChange + * @property {boolean?} updateTextOnChange + * @property {boolean?} useAny + * @property {string?} className + * */ + + /** + * Multiselect dropdown component. + * + * @param {MultiselectDropdownComponentModel} params + * @returns {HTMLElement} + * @constructor + */ + const MultiselectDropdownComponent = ({ + options, + name = '', + values = [], + placeholder, + selectedText, + onChange, + updateTextOnChange = true, + useAny = true, + className = '' + }) => { + const { elementGenerator: generator, translationService } = AdyenFE; + + options.forEach((option) => { + option.label = translationService.translate(option.label); + }); + + const handleDisplayedItems = (fireChange = true) => { + hiddenInput.value = selectedItems.map((item) => item.value).join(','); + if (useAny) { + const anyItem = list.querySelector('.adlt--any'); + if (selectedItems.length > 0) { + anyItem?.classList.remove('adls--selected'); + } else { + anyItem.classList.toggle('adls--selected'); + + list.querySelectorAll(':not(.adlt--any)').forEach((listItem) => { + listItem.classList.remove('adls--selected'); + if (anyItem.classList.contains('adls--selected')) { + listItem.classList.add('adls--disabled'); + } else { + listItem.classList.remove('adls--disabled'); + } + }); + } + } + + let textToDisplay; + if (selectedItems.length > 2) { + textToDisplay = translationService.translate(selectedText, [selectedItems.length]); + } else { + textToDisplay = + selectedItems.map((item) => item.label).join(', ') || translationService.translate(placeholder); + } + + updateTextOnChange && (selectButton.firstElementChild.innerHTML = textToDisplay); + fireChange && onChange?.(selectedItems.map((item) => item.value)); + }; + + const createListItem = (additionalClass, label, htmlKey) => { + const item = generator.createElement('li', `adlp-dropdown-list-item ${additionalClass}`, label, htmlKey, [ + generator.createElement('input', 'adlp-checkbox', '', { type: 'checkbox' }) + ]); + list.append(item); + return item; + }; + + const renderOption = (option) => { + const listItem = createListItem(values?.includes(option.value) ? 'adls--selected' : '', option.label, null); + + selectedItems.forEach((item) => { + if (option.value === item.value) { + listItem.classList.add('adls--selected'); + } + }); + + listItem.addEventListener('click', () => { + listItem.classList.toggle('adls--selected'); + listItem.childNodes[0].checked = listItem.classList.contains('adls--selected'); + if (!selectedItems.includes(option)) { + selectedItems.push(option); + } else { + const index = selectedItems.indexOf(option); + selectedItems.splice(index, 1); + } + + handleDisplayedItems(); + }); + }; + + let selectedItems = options.filter((option) => values?.includes(option.value)); + + const hiddenInput = generator.createElement('input', 'adlp-hidden-input', '', { + type: 'hidden', + name, + value: values?.join(',') || '' + }); + const wrapper = generator.createElement('div', 'adl-multiselect-dropdown' + (className ? ' ' + className : '')); + const selectButton = generator.createElement( + 'button', + 'adlp-dropdown-button adlp-field-component', + '', + { + type: 'button' + }, + [generator.createElement('span', selectedItems ? 'adls--selected' : '', placeholder)] + ); + + const list = generator.createElement('ul', 'adlp-dropdown-list'); + if (useAny) { + const anyItem = createListItem( + 'adlt--any' + (!values?.length ? ' adls--selected' : ''), + 'general.any', + null + ); + + anyItem.addEventListener('click', () => { + selectedItems = []; + anyItem.childNodes[0].checked = anyItem.classList.contains('adls--selected'); + + handleDisplayedItems(); + }); + } + + options.forEach(renderOption); + + selectButton.addEventListener('click', (event) => { + preventDefaults(event); + list.classList.toggle('adls--show'); + wrapper.classList.toggle('adls--active'); + }); + + window.addEventListener('click', (event) => { + if (!list.contains(event.target) && event.target !== list) { + list.classList.remove('adls--show'); + wrapper.classList.remove('adls--active'); + } + }); + + wrapper.append(hiddenInput, selectButton, list); + + values?.length && handleDisplayedItems(false); + + return wrapper; + }; + + AdyenFE.components.MultiselectDropdown = { + /** + * @param {MultiselectDropdownComponentModel} config + * @returns {HTMLElement} + */ + create: (config) => MultiselectDropdownComponent(config) + }; +})(); diff --git a/Resources/views/backend/_resources/js/NotificationsController.js b/Resources/views/backend/_resources/js/NotificationsController.js new file mode 100644 index 00000000..06183a99 --- /dev/null +++ b/Resources/views/backend/_resources/js/NotificationsController.js @@ -0,0 +1,415 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef ShopNotification + * @property {string} orderId + * @property {string} paymentMethod + * @property {string} severity + * @property {string} dateAndTime + * @property {string} message + * @property {string} details + */ + + /** + * @typedef ShopNotifications + * @property {boolean} nextPageAvailable + * @property {ShopNotification[]} notifications + */ + + /** + * @typedef WebhookNotification + * @property {string} orderId + * @property {string} logo + * @property {string} paymentMethod + * @property {string} notificationID + * @property {string} dateAndTime + * @property {string} code + * @property {boolean} successful + * @property {string} status + * @property {boolean} hasDetails + * @property {{reason: string, failureDescription: string, adyenLink: string, shopLink: string}?} details + */ + + /** + * @typedef WebhookNotifications + * @property {boolean} nextPageAvailable + * @property {WebhookNotification[]} notifications + */ + + /** + * Handles notification pages logic. + * + * @param {{ + * getShopEventsNotifications: string, + * getWebhookEventsNotifications : string, + * page: string}} configuration + * @constructor + */ + function NotificationsController(configuration) { + /** @type AjaxServiceType */ + const api = AdyenFE.ajaxService; + + const { templateService, elementGenerator: generator, utilities, components } = AdyenFE; + const dataTableComponent = components.DataTable; + + /** @type string */ + let currentStoreId = ''; + + let nextPageAvailable = true; + let currentlyLoading = false; + let page = 1; + const limit = 10; + + /** + * Displays page content. + * + * @param {{state?: string, storeId: string}} config + */ + this.display = ({ storeId }) => { + currentStoreId = storeId; + templateService.clearMainPage(); + + configuration.getShopEventsNotifications = configuration.getShopEventsNotifications.replace( + '{storeId}', + storeId + ); + configuration.getWebhookEventsNotifications = configuration.getWebhookEventsNotifications.replace( + '{storeId}', + storeId + ); + + return renderPage(); + }; + + /** + * Sets the unsaved changes. + * + * @return {boolean} + */ + this.hasUnsavedChanges = () => false; + + const renderPage = () => { + utilities.showLoader(); + let url; + let renderer; + + templateService.clearMainPage(); + + switch (configuration.page) { + case 'shop': + url = `${configuration.getShopEventsNotifications}?page=${page}&limit=${limit}`; + renderer = renderShopNotificationsTable; + break; + case 'webhook': + url = `${configuration.getWebhookEventsNotifications}?page=${page}&limit=${limit}`; + renderer = renderWebhookNotificationsTable; + break; + } + + return api + .get(url, () => {}) + .then(renderer) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Renders a modal to display notification details. + * + * @param {WebhookNotification} webhookNotification + */ + const renderDetailsModal = (webhookNotification) => { + const modal = components.Modal.create({ + title: 'notifications.webhook.notificationDetailsModal.title', + className: 'adl-webhook-notifications-modal', + content: [ + generator.createElement('p', 'adlp-reason', '', null, [ + generator.createElement( + 'span', + 'adlp-reason-title', + 'notifications.webhook.notificationDetailsModal.reason' + ), + generator.createElement('span', 'adlp-reason-text', webhookNotification.details.reason) + ]), + generator.createElement('p', 'adlp-failure-description', '', null, [ + generator.createElement( + 'span', + 'adlp-failure-description-title', + 'notifications.webhook.notificationDetailsModal.failureDescription' + ), + generator.createElement( + 'span', + 'adlp-failure-description-text', + webhookNotification.details.failureDescription + ) + ]), + generator.createElement( + 'a', + 'adlp-adyen-link', + '', + { href: webhookNotification.details.adyenLink, target: '_blank' }, + [ + generator.createElement( + 'span', + '', + 'notifications.webhook.notificationDetailsModal.paymentLink' + ) + ] + ), + generator.createElement( + 'a', + 'adlp-shop-link', + '', + { href: webhookNotification.details.shopLink, target: '_blank' }, + [generator.createElement('span', '', 'notifications.webhook.notificationDetailsModal.shopLink')] + ) + ], + footer: true, + canClose: true, + buttons: [ + { + type: 'primary', + label: 'general.ok', + onClick: () => modal.close() + } + ] + }); + + modal.open(); + }; + + /** + * Renders shop table rows. + * + * @param {ShopNotification[]} shopNotifications + * @returns {TableCell[][]} + */ + const getRowsConfig = (shopNotifications) => { + return shopNotifications?.map((notification) => { + return [ + { + label: notification.orderId, + className: 'adlm--left-aligned' + }, + { + label: notification.paymentMethod, + className: 'adlm--left-aligned adlm--blue-text' + }, + { + renderer: (cell) => + cell.append( + generator.createElement( + 'span', + `adlp-status adlt--${notification.severity}`, + `notifications.shop.severity.${notification.severity}` + ) + ), + className: 'adlm--left-aligned' + }, + { + label: notification.dateAndTime, + className: 'adlm--left-aligned' + }, + { + label: `notifications.shop.` + notification.message, + className: 'adlm--left-aligned' + }, + { + label: `notifications.shop.` + notification.details, + className: 'adlm--left-aligned' + } + ]; + }); + }; + + /** + * Renders webhook table rows. + * + * @param {WebhookNotification[]} webhookNotifications + * @returns {TableCell[][]} + */ + const getWebhookRowsConfig = (webhookNotifications) => { + return webhookNotifications?.map((notification) => { + const options = { + day: 'numeric', + month: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: false + }; + + const formattedDateTime = new Date(notification.dateAndTime) + .toLocaleString('en-US', options) + .replace(/, /g, ' ') + .replace(/\//g, '-'); + + return [ + { + label: notification.orderId, + className: 'adlm--left-aligned' + }, + { + className: 'adlm--left-aligned', + renderer: (cell) => + cell.prepend( + generator.createElement('img', 'adlp-payment-logo', '', { src: notification.logo }) + ) + }, + { + label: notification.paymentMethod, + className: 'adlm--left-aligned adlm--blue-text' + }, + { + label: notification.code, + className: 'adlm--left-aligned' + }, + { + label: formattedDateTime, + className: 'adlm--left-aligned' + }, + { + label: notification.successful ? 'general.yes' : 'general.no', + className: 'adlm--left-aligned' + }, + { + renderer: (cell) => + cell.append( + generator.createElement( + 'span', + `adlp-status adlt--${notification.status}`, + `notifications.webhook.status.${notification.status}` + ) + ), + className: 'adlm--left-aligned' + }, + notification.status === 'failed' + ? { + className: 'adlm--left-aligned', + renderer: (cell) => + cell.append( + generator.createButton({ + type: 'primary', + className: 'adl-button adlt--ghost adlm--blue', + label: 'general.viewDetails', + onClick: () => renderDetailsModal(notification) + }) + ) + } + : {} + ]; + }); + }; + + /** + * Renders the shop notifications table. + * + * @param {ShopNotifications} shopNotificationsPage + */ + const renderShopNotificationsTable = (shopNotificationsPage) => { + const headers = [ + 'notifications.shop.shopEventsNotifications.orderId', + 'notifications.shop.shopEventsNotifications.paymentMethod', + 'notifications.shop.shopEventsNotifications.severity', + 'notifications.shop.shopEventsNotifications.dateAndTime', + 'notifications.shop.shopEventsNotifications.message', + 'notifications.shop.shopEventsNotifications.details' + ]; + + createNotifications(headers, getRowsConfig, 'Shop', shopNotificationsPage); + }; + + /** + * Renders the webhook notifications table. + * + * @param {WebhookNotifications} webhookNotificationsPage + */ + const renderWebhookNotificationsTable = (webhookNotificationsPage) => { + const headers = [ + 'notifications.webhook.webhookEventsNotifications.orderId', + 'notifications.webhook.webhookEventsNotifications.logo', + 'notifications.webhook.webhookEventsNotifications.paymentMethod', + 'notifications.webhook.webhookEventsNotifications.eventCode', + 'notifications.webhook.webhookEventsNotifications.dateAndTime', + 'notifications.webhook.webhookEventsNotifications.success', + 'notifications.webhook.webhookEventsNotifications.status', + 'notifications.webhook.webhookEventsNotifications.action' + ]; + createNotifications(headers, getWebhookRowsConfig, 'Webhook', webhookNotificationsPage); + }; + + /** + * Returns a function that renders a notifications table and handles pagination. + * + * @param {string[]} headers The table headers. + * @param {(notifications: any[]) => TableCell[][]} getRowsConfig A function that maps notifications to table + * rows. + * @param {string} type The type of notifications. + * @param {ShopNotifications | WebhookNotifications} notificationsPage Notifications page. + */ + const createNotifications = (headers, getRowsConfig, type, notificationsPage) => { + const typeLc = type.toLowerCase(); + nextPageAvailable = notificationsPage.nextPageAvailable; + page = 1; + currentlyLoading = false; + + const headerCells = headers.map((headerLabel) => ({ + label: headerLabel, + className: 'adlm--left-aligned' + })); + + const rows = getRowsConfig(notificationsPage.notifications); + + templateService + .getMainPage() + .append( + generator.createElement('div', `adl-notifications-page`, '', null, [ + generator.createElement('h2', '', `notifications.${typeLc}.title`), + generator.createElement('p', '', `notifications.${typeLc}.description`), + rows.length + ? dataTableComponent.createDataTable(headerCells, rows, `adl-notifications-table`) + : dataTableComponent.createNoItemsMessage(`notifications.${typeLc}.noNotificationsMessage`) + ]) + ); + + const tableWrapper = document.querySelector( + `.adl-notifications-page .adl-notifications-table .adl-table-wrapper` + ); + + tableWrapper?.addEventListener('scroll', (event) => { + if ( + nextPageAvailable && + !currentlyLoading && + tableWrapper.scrollTop + tableWrapper.clientHeight > tableWrapper.scrollHeight - 10 + ) { + page++; + currentlyLoading = true; + + let spinnerWrapper = generator.createElement('div', 'adl-loader adlt--large', '', '', [ + generator.createElement('div', 'adlp-spinner') + ]); + tableWrapper.append(spinnerWrapper); + + api.get(`${configuration[`get${type}EventsNotifications`]}?page=${page}&limit=${limit}`, () => null) + .then((newPage) => { + nextPageAvailable = newPage?.nextPageAvailable; + + !!newPage?.notifications?.length && + dataTableComponent.createTableRows(event.target, getRowsConfig(newPage.notifications)); + }) + .catch(console.error) + .finally(() => { + spinnerWrapper.remove(); + currentlyLoading = false; + }); + } + }); + }; + } + + AdyenFE.NotificationsController = NotificationsController; +})(); diff --git a/Resources/views/backend/_resources/js/PageControllerFactory.js b/Resources/views/backend/_resources/js/PageControllerFactory.js new file mode 100644 index 00000000..c8e5aa6c --- /dev/null +++ b/Resources/views/backend/_resources/js/PageControllerFactory.js @@ -0,0 +1,28 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + function PageControllerFactory() { + /** + * Instantiates page controller; + * + * @param {string} controller + * @param {Record} configuration + */ + this.getInstance = (controller, configuration) => { + let parts = controller.split('-'); + let name = ''; + for (let part of parts) { + part = part.charAt(0).toUpperCase() + part.slice(1); + name += part; + } + + name += 'Controller'; + + return AdyenFE[name] ? new AdyenFE[name](configuration) : null; + }; + } + + AdyenFE.pageControllerFactory = new PageControllerFactory(); +})(); diff --git a/Resources/views/backend/_resources/js/PaymentsController.js b/Resources/views/backend/_resources/js/PaymentsController.js new file mode 100644 index 00000000..ca103283 --- /dev/null +++ b/Resources/views/backend/_resources/js/PaymentsController.js @@ -0,0 +1,1542 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef PaymentMethod + * @property {string} methodId + * @property {string} name + * @property {string} code + * @property {string?} methodName + * @property {string} logo + * @property {'creditOrDebitCard' | 'buyNowPayLater' | 'cashOrAtm' | 'directDebit' | 'onlinePayments' | 'wallet' | + * 'prepaidAndGiftCard' | 'mobile'} paymentType + * @property {boolean} status + * @property {string[]} currencies + * @property {string[]} countries + */ + + /** + * Map between a method code and a method identifier on Adyen + * + * @type {Record} + */ + const methodMap = { + ach: 'ach-direct-debit', + alipay: 'alipay', + amazonpay: 'amazon-pay', + applepay: 'apple-pay', + blik: 'blik', + directEbanking: 'cards', + ebanking_FI: 'finland-online-banking', + eps: 'eps', + giropay: 'giropay', + googlepay: 'google-pay', + ideal: 'ideal', + klarna: 'klarna', + klarna_account: 'klarna', + klarna_paynow: 'klarna', + mbway: 'mb-way', + mobilepay: 'mobilepay', + momo_wallet: 'momo-wallet', + multibanco: 'multibanco', + molpay_ebanking_TH: 'online-banking-thailand', + oney: 'oney', + onlineBanking_PL: 'online-banking-poland', + paypal: 'paypal', + paysafecard: 'paysafecard', + paywithgoogle: 'google-pay', + ratepay: 'ratepay', + ratepay_directdebit: 'ratepay', + scheme: 'cards', + sepadirectdebit: 'sepa-direct-debit', + swish: 'swish', + trustly: 'trustly', + twint: 'twint', + vipps: 'vipps' + }; + + const methodTypes = [ + 'creditOrDebitCard', + 'buyNowPayLater', + 'cashOrAtm', + 'directDebit', + 'onlinePayments', + 'wallet', + 'prepaidAndGiftCard', + 'mobile' + ]; + + const currencies = [ + 'AED', + 'AUD', + 'BGN', + 'BHD', + 'BRL', + 'CAD', + 'CHF', + 'CNY', + 'CZK', + 'DKK', + 'EUR', + 'GBP', + 'HKD', + 'HUF', + 'ISK', + 'ILS', + 'INR', + 'JOD', + 'JPY', + 'KRW', + 'KWD', + 'MYR', + 'NOK', + 'NZD', + 'OMR', + 'PLN', + 'QAR', + 'RON', + 'RUB', + 'SAR', + 'SEK', + 'SGD', + 'THB', + 'TWD', + 'USD', + 'ZAR' + ]; + + const countries = [ + 'AU', + 'AT', + 'BE', + 'BG', + 'CA', + 'HR', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GI', + 'GR', + 'HK', + 'HU', + 'IS', + 'IE', + 'IT', + 'JP', + 'LV', + 'LI', + 'LT', + 'LU', + 'MT', + 'NL', + 'NZ', + 'NO', + 'PL', + 'PT', + 'PR', + 'RO', + 'SG', + 'SK', + 'SI', + 'ES', + 'SE', + 'CH', + 'AE', + 'GB', + 'US' + ]; + + /** + * @typedef AdditionalDataConfig + * @property {boolean?} showLogos + * @property {boolean?} singleClickPayment + * @property {boolean?} sendBasket + * @property {boolean?} installments + * @property {boolean?} installmentAmounts + * @property {string[]?} installmentCountries + * @property {string?} supportedInstallments + * @property {number?} minimumAmount + * @property {string?} numberOfInstallments + * @property {string?} bankIssuer + * @property {string?} merchantId + * @property {string?} publicKeyId + * @property {string?} storeId + * @property {string?} gatewayMerchantId + * @property {string?} merchantName + * @property {boolean?} displayButtonOn + */ + + /** + * @typedef PaymentMethodConfiguration + * @property {boolean} isNew + * @property {string} methodId + * @property {string} code + * @property {string?} name + * @property {string?} description + * @property { 'none' | 'fixed' | 'percent' | 'combined' } surchargeType + * @property {number?} fixedSurcharge + * @property {number?} percentSurcharge + * @property {number?} surchargeLimit + * @property {string?} logo + * @property {Blob?} logoFile + * @property {'creditOrDebitCard' | 'buyNowPayLater' | 'cashOrAtm' | 'directDebit' | 'onlinePayments' | 'wallet' | + * 'prepaidAndGiftCard' | 'mobile'} paymentType + * @property {AdditionalDataConfig?} additionalData + */ + /** + * Handles payments pages logic. + * + * @param {{getConfiguredPaymentsUrl: string, getAvailablePaymentsUrl: string, addMethodConfigurationUrl: string, + * saveMethodConfigurationUrl: string, getMethodConfigurationUrl: string, deleteMethodConfigurationUrl: string + * }} configuration + * @constructor + */ + function PaymentsController(configuration) { + /** @type AjaxServiceType */ + const api = AdyenFE.ajaxService; + + const { + templateService, + translationService, + elementGenerator: generator, + validationService: validator, + components, + utilities + } = AdyenFE; + + const dataTableComponent = AdyenFE.components.DataTable; + + /** @type {HTMLElement} */ + let page; + + /** @type {Record} */ + let activeFilters = {}; + + /** @type {PaymentMethod[]} */ + let activeMethods = []; + + /** @type {PaymentMethod[]} */ + let availableMethods = []; + + /** @type {PaymentMethodConfiguration | null} */ + let activeMethod = null; + + /** @type {PaymentMethodConfiguration | null} */ + let changedMethod = null; + + /** @type {number} */ + let numberOfChanges = 0; + + /** + * Replaces an active page with the other one rendered by a provider renderer method. + * + * @param {() => void} renderer + */ + const switchPage = (renderer) => { + utilities.showLoader(); + document.querySelector('.adl-form-footer')?.remove(); + if (!page) { + page = generator.createElement('div', 'adl-payments-page'); + } else { + templateService.clearComponent(page); + } + + activeFilters = {}; + renderer(); + }; + + /** + * Creates payment methods table. + * + * @param {PaymentMethod[]} paymentMethods + * @param {(cell: HTMLElement, method: PaymentMethod) => void} actionsCellRenderer + * @return {HTMLElement} + */ + const createMethodsTable = (paymentMethods, actionsCellRenderer) => { + /** + * @param {string[]} items + */ + const createHintCell = (items) => { + return (cell) => { + return cell.append( + generator.createHint( + `payments.list.multiItemLabel|${items[0]}|${items.length - 1}`, + items.join(', '), + 'bottom' + ) + ); + }; + }; + + /** @type {TableCell[]} */ + const header = [ + { + label: 'payments.list.paymentMethod', + className: 'adlm--left-aligned' + }, + { + label: 'payments.list.currencies' + }, + { + label: 'payments.list.regions' + }, + { + label: 'payments.list.type' + }, + { + renderer: (cell) => + cell.append( + generator.createElement('div', 'adlp-status-header', 'payments.list.status', null, [ + generator.createHint('', 'payments.list.statusHint', 'top') + ]) + ) + }, + { + label: 'payments.list.actions' + } + ]; + + /** @type {TableCell[][]} */ + const rows = paymentMethods.map((method) => { + return [ + { + label: method.name, + className: 'adlm--left-aligned', + renderer: (cell) => + cell.prepend(generator.createElement('img', 'adlp-payment-logo', '', { src: method.logo })) + }, + { + label: method.currencies?.length <= 2 ? method.currencies?.join(', ') : '', + renderer: method.currencies?.length > 2 ? createHintCell(method.currencies) : null + }, + { + label: method.countries?.length <= 2 ? method.countries?.join(', ') : '', + renderer: method.countries?.length > 2 ? createHintCell(method.countries) : null + }, + { + label: `payments.paymentTypes.${method.paymentType}` + }, + { + renderer: (cell) => + cell.append( + generator.createElement( + 'span', + 'adlp-status adlt--' + (method.status ? 'active' : 'inactive'), + `payments.list.status${method.status ? 'Active' : 'Inactive'}` + ) + ) + }, + { + renderer: (cell) => actionsCellRenderer(cell, method) + } + ]; + }); + + return dataTableComponent.createPaymentsDataTable(header, rows); + }; + + /** + * Renders or replaces the methods table. + * + * @param {HTMLElement} table + */ + const renderMethodsTable = (table) => { + page.querySelector('.adlp-no-items-wrapper')?.remove(); + const existingTable = page.querySelector('.adl-table-wrapper'); + const backButton = page.querySelector('.adlp-back-button'); + if (existingTable) { + existingTable.parentElement?.remove(); + } + + if (backButton) { + page.insertBefore(table, backButton); + } else { + page.append(table); + } + }; + + /** + * Filters methods based on the current filter. + * + * @param {PaymentMethod[]} methods + * @returns {PaymentMethod[]} + */ + const applyFilter = (methods) => { + return methods.filter((method) => { + if (activeFilters.types?.length && !activeFilters.types.includes(method.paymentType)) { + return false; + } + + if ( + activeFilters.status?.length && + !activeFilters.status.includes(method.status ? 'active' : 'inactive') + ) { + return false; + } + + if (activeFilters.currencies?.length) { + return method.currencies.reduce( + (result, code) => result || activeFilters.currencies.includes(code) || code === 'ANY', + false + ); + } + + if (activeFilters.countries?.length) { + return method.countries.reduce( + (result, code) => result || activeFilters.countries.includes(code) || code === 'ANY', + false + ); + } + + return true; + }); + }; + + /** + * Renders the active payments form. + */ + const renderActivePaymentsForm = () => { + page.append( + generator.createElement('div', 'adl-payment-methods-header', '', null, [ + generator.createElement('div', '', '', null, [ + generator.createElement('h2', '', 'payments.active.title'), + generator.createElement('p', '', 'payments.active.description') + ]), + generator.createButton({ + type: 'primary', + className: 'adlp-add-methods-button', + label: 'payments.active.addMethod', + onClick: () => switchPage(renderChooseMethodPage) + }) + ]) + ); + + api.get(configuration.getConfiguredPaymentsUrl) + .then((methods) => { + activeMethods = methods; + if (!methods?.length) { + page.append(dataTableComponent.createNoItemsMessage('payments.active.noMethodsMessage')); + templateService.getMainPage().append(page); + } else { + return api.get(configuration.getAvailablePaymentsUrl).then((allMethods) => { + allMethods.forEach((m) => (m.methodName = m.name)); + availableMethods = allMethods; + + page.append(renderPaymentsTableFilter(renderActiveMethodsTable)); + renderActiveMethodsTable(); + + templateService.getMainPage().append(page); + }); + } + }) + .catch(() => false) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Renders table for the active payment methods. + */ + const renderActiveMethodsTable = () => { + const data = applyFilter( + activeMethods.map((method) => ({ + ...availableMethods.find((m) => m.methodId === method.methodId), + ...method + })) + ); + if (data.length) { + renderMethodsTable( + createMethodsTable(data, (cell, method) => { + cell.append( + generator.createButton({ + type: 'ghost', + size: 'small', + className: 'adlt--edit-button', + label: 'general.edit', + onClick: () => switchPage(() => renderPaymentConfigForm(method)) + }), + generator.createButton({ + type: 'ghost', + size: 'small', + className: 'adlt--delete-button adlm--destructive', + label: 'general.delete', + onClick: () => renderDeleteModal(method) + }) + ); + }) + ); + } else { + renderMethodsTable(dataTableComponent.createNoItemsMessage('payments.list.noMethodsForFilter')); + } + }; + + /** + * Handles choosing method page. + */ + const renderChooseMethodPage = () => { + page.append( + generator.createElement('h2', '', 'payments.add.title'), + generator.createElement('p', '', 'payments.add.description'), + renderPaymentsTableFilter(renderAvailableMethodsTable) + ); + + api.get(configuration.getAvailablePaymentsUrl) + .then((methods) => { + return api.get(configuration.getConfiguredPaymentsUrl).then((configuredPayments) => { + // filter out already configured methods. + methods = (methods || []).filter( + (a) => !(configuredPayments || []).find((c) => c.methodId === a.methodId) + ); + + availableMethods = methods; + + if (!methods?.length) { + page.append(dataTableComponent.createNoItemsMessage('payments.add.noMethodsMessage')); + } else { + renderAvailableMethodsTable(); + } + + templateService.getMainPage().append(page); + }); + }) + .finally(() => { + page.append( + generator.createButton({ + type: 'secondary', + label: 'payments.add.back', + className: 'adlp-back-button', + onClick: () => switchPage(renderActivePaymentsForm) + }) + ); + utilities.hideLoader(); + }); + }; + + /** + * Renders the list of available payment methods + */ + const renderAvailableMethodsTable = () => { + const data = applyFilter(availableMethods); + if (data.length) { + renderMethodsTable( + createMethodsTable(data, (cell, method) => { + cell.append( + generator.createButton({ + type: 'secondary', + size: 'small', + className: 'adlt--add-button adlm--blue adlm--no-wrap', + label: 'payments.list.actionsAdd', + onClick: () => switchPage(() => renderPaymentConfigForm(method)) + }) + ); + }) + ); + } else { + renderMethodsTable(dataTableComponent.createNoItemsMessage('payments.list.noMethodsForFilter')); + } + }; + + /** + * Creates payments table filer. + */ + const renderPaymentsTableFilter = (renderer) => { + let container = page.querySelector('.adlp-table-filter-wrapper'); + let filters; + if (!container) { + filters = generator.createElement('div', 'adlp-table-filters'); + container = generator.createElement('div', 'adlp-table-filter-wrapper', '', null, [ + generator.createButton({ + type: 'ghost', + size: 'medium', + className: 'adlm--blue adlp-filters-switch-button', + label: 'payments.list.filter', + onClick: () => { + container.classList.toggle('adls--filters-active'); + } + }), + filters + ]); + } else { + filters = container.querySelector('.adlp-table-filters'); + filters.innerHTML = ''; + } + + const changeFilter = (filter, values) => { + activeFilters[filter] = values; + resetButton.disabled = + Object.values(activeFilters).reduce((result, options) => result + options.length, 0) === 0; + renderer(); + }; + + const resetButton = generator.createButton({ + type: 'ghost', + label: 'payments.filter.resetAll', + size: 'small', + className: 'adlp-reset-button', + disabled: true, + onClick: () => { + activeFilters = {}; + renderPaymentsTableFilter(renderer); + renderer(); + resetButton.disabled = true; + } + }); + + filters.append( + ...[ + components.TableFilter.create({ + name: 'types', + isMultiselect: true, + label: translationService.translate('payments.filter.types.label'), + labelPlural: translationService.translate('payments.filter.types.labelPlural'), + values: activeFilters.types || [], + options: methodTypes.map((key) => ({ + value: key, + label: translationService.translate(`payments.paymentTypes.${key}`) + })), + selectPlaceholder: 'payments.filter.types.selectPlaceholder', + onChange: (values) => changeFilter('types', values) + }), + components.TableFilter.create({ + name: 'status', + isMultiselect: false, + label: translationService.translate('payments.filter.statuses.label'), + labelPlural: translationService.translate('payments.filter.statuses.labelPlural'), + values: activeFilters.status || [], + options: [ + { + value: 'active', + label: translationService.translate(`payments.list.statusActive`) + }, + { + value: 'inactive', + label: translationService.translate(`payments.list.statusInactive`) + } + ], + selectPlaceholder: 'payments.filter.statuses.selectPlaceholder', + onChange: (values) => changeFilter('status', values) + }), + components.TableFilter.create({ + name: 'currencies', + isMultiselect: true, + label: translationService.translate('payments.filter.currencies.label'), + labelPlural: translationService.translate('payments.filter.currencies.labelPlural'), + values: activeFilters.currencies || [], + options: currencies.map((c) => ({ value: c, label: c })), + selectPlaceholder: 'payments.filter.currencies.selectPlaceholder', + onChange: (values) => changeFilter('currencies', values) + }), + components.TableFilter.create({ + name: 'countries', + isMultiselect: true, + label: translationService.translate('payments.filter.countries.label'), + labelPlural: translationService.translate('payments.filter.countries.labelPlural'), + values: activeFilters.countries || [], + options: countries.map((c) => ({ + value: c, + label: translationService.translate(`countries.${c}`) + })), + selectPlaceholder: 'payments.filter.countries.selectPlaceholder', + onChange: (values) => changeFilter('countries', values) + }), + resetButton + ] + ); + + return container; + }; + + /** + * Gets the default payment method configuration. + * + * @param {PaymentMethod} method + * @returns {PaymentMethodConfiguration} + */ + const getDefaultConfig = (method) => { + const config = { + isNew: true, + methodId: method.methodId, + code: method.code, + paymentType: method.paymentType, + logo: method.logo, + name: method.methodName || method.name, + description: 'Adyen ' + (method.methodName || method.name), + surchargeType: 'none', + additionalData: {} + }; + + if (method.paymentType === 'creditOrDebitCard') { + config.additionalData = { + showLogos: true, + singleClickPayment: true, + sendBasket: true, + installments: false, + installmentAmounts: false, + numberOfInstallments: '', + installmentCountries: ['ANY'], + minimumAmount: null + }; + } + + switch (method.code) { + case 'molpay_ebanking_TH': + config.additionalData = { + showLogos: true, + bankIssuer: '' + }; + break; + case 'ideal': + config.additionalData = { + showLogos: true, + bankIssuer: '' + }; + break; + case 'eps': + config.additionalData = { + bankIssuer: '' + }; + break; + case 'applepay': + config.additionalData = { + merchantId: '', + merchantName: '', + displayButtonOn: true + }; + break; + case 'amazonpay': + config.additionalData = { + publicKeyId: '', + merchantId: '', + storeId: '', + displayButtonOn: true + }; + break; + case 'googlepay': + case 'paywithgoogle': + config.additionalData = { + merchantId: '', + gatewayMerchantId: '', + displayButtonOn: true + }; + break; + case 'paypal': + config.additionalData = { + displayButtonOn: true + }; + break; + } + + return config; + }; + + /** + * Renders the config form for the given payment method. + * + * @param {PaymentMethod} method + */ + const renderPaymentConfigForm = (method) => { + page.append( + generator.createElement('div', 'adlp-configure-method-header', '', null, [ + generator.createElement( + 'h2', + '', + 'payments.configure.title|' + (method.methodName || method.name), + null, + [ + generator.createElement( + 'div', + 'adlp-status-badge adlt--' + (method.status ? 'active' : 'inactive'), + 'payments.list.status' + (method.status ? 'Active' : 'Inactive') + ) + ] + ), + generator.createElement('img', 'adlp-payment-logo', '', { src: method.logo }) + ]), + generator.createElement( + 'p', + '', + 'payments.configure.description|' + (methodMap[method.code] || method.code) + ), + generator.createElement('p', 'adlp-flash-message-wrapper') + ); + + templateService.getMainPage().append(page); + + api.get(configuration.getMethodConfigurationUrl.replace('{methodId}', method.methodId), (error) => { + if (error.status === 404) { + return Promise.resolve(null); + } + + throw error; + }) + .then( + /** @param {PaymentMethodConfiguration} config */ + (config) => { + activeMethod = utilities.cloneObject(config); + if (!config?.code) { + config = getDefaultConfig(method); + activeMethod = utilities.cloneObject(config); + + numberOfChanges = 2; + } else { + config.isNew = false; + config.paymentType = method.paymentType; + + numberOfChanges = 0; + } + + changedMethod = utilities.cloneObject(config); + + renderCommonConfigForm(); + + switch (method.paymentType) { + case 'creditOrDebitCard': + renderCreditCardForm(); + renderInstallmentsForm(); + } + + switch (method.code) { + case 'oney': + renderOneyForm(); + break; + case 'molpay_ebanking_TH': + case 'ideal': + renderIssuersConfigurationForm(); + break; + case 'eps': + renderEpsForm(); + break; + case 'applepay': + renderApplePayForm(); + break; + case 'amazonpay': + renderAmazonPayForm(); + break; + case 'googlepay': + case 'paywithgoogle': + renderGooglePayForm(); + break; + case 'paypal': + renderPayPalForm(); + break; + } + + page.append(generator.createFormFooter(handleSavePaymentMethod, navigateToPaymentsForm)); + renderFooterState(); + handleDependencies('installments', config.additionalData.installments, true); + } + ) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Renders the common payment method config form. + */ + const renderCommonConfigForm = () => { + page.append( + generator.createElement('div', 'adlp-separator'), + ...generator.createFormFields( + [ + { + name: 'name', + value: changedMethod.name, + type: 'text', + label: 'payments.configure.fields.name.label', + description: 'payments.configure.fields.name.description', + error: 'validation.requiredField' + }, + { + name: 'description', + value: changedMethod.description, + type: 'text', + label: 'payments.configure.fields.description.label', + description: 'payments.configure.fields.description.description', + error: 'validation.requiredField' + }, + { + name: 'surchargeType', + value: changedMethod.surchargeType, + type: 'dropdown', + label: 'payments.configure.fields.surchargeType.label', + description: 'payments.configure.fields.surchargeType.description', + placeholder: 'payments.configure.fields.surchargeType.placeholder', + options: [ + { label: 'payments.configure.fields.surchargeType.none', value: 'none' }, + { label: 'payments.configure.fields.surchargeType.fixed', value: 'fixed' }, + { label: 'payments.configure.fields.surchargeType.percent', value: 'percent' }, + { label: 'payments.configure.fields.surchargeType.combined', value: 'combined' } + ] + }, + { + name: 'fixedSurcharge', + value: changedMethod.fixedSurcharge, + type: 'number', + dataset: { + validationRule: 'nonNegative' + }, + label: 'payments.configure.fields.fixedSurcharge.label', + description: 'payments.configure.fields.fixedSurcharge.description', + error: 'validation.numeric' + }, + { + name: 'percentSurcharge', + value: changedMethod.percentSurcharge, + type: 'number', + dataset: { + validationRule: 'nonNegative' + }, + label: 'payments.configure.fields.percentSurcharge.label', + description: 'payments.configure.fields.percentSurcharge.description', + error: 'validation.numeric' + }, + { + name: 'surchargeLimit', + value: changedMethod.surchargeLimit, + type: 'number', + dataset: { + validationRule: 'greaterThanZero,greaterThanX|fixedSurcharge' + }, + label: 'payments.configure.fields.surchargeLimit.label', + description: 'payments.configure.fields.surchargeLimit.description', + error: 'payments.configure.fields.surchargeLimit.error' + }, + { + name: 'logo', + value: changedMethod.logo, + type: 'file', + supportedMimeTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml', 'image/svg'], + label: 'payments.configure.fields.logo.label', + placeholder: 'payments.configure.fields.logo.placeholder' + } + ].map((config) => ({ + ...config, + onChange: (value) => handleConfigMethodChange(config.name, value) + })) + ) + ); + + handleDependencies('surchargeType', changedMethod.surchargeType); + }; + + /** + * Renders the credit card config form. + */ + const renderCreditCardForm = () => { + page.append( + ...generator.createFormFields([ + getRadioField('creditCardFields', 'showLogos'), + getRadioField('creditCardFields', 'singleClickPayment'), + getRadioField('creditCardFields', 'sendBasket') + ]) + ); + }; + + /** + * Renders the Apple Pay config form. + */ + const renderApplePayForm = () => { + page.append( + ...generator.createFormFields([ + getTextField('applePayFields', 'merchantId'), + getTextField('applePayFields', 'merchantName'), + getRadioField('applePayFields', 'displayButtonOn') + ]) + ); + }; + + /** + * Renders the Amazon Pay config form. + */ + const renderAmazonPayForm = () => { + page.append( + ...generator.createFormFields([ + getTextField('amazonPayFields', 'publicKeyId', 'adl-amazon-pay-public-key-id'), + getTextField('amazonPayFields', 'merchantId', 'adl-amazon-pay-merchant-id'), + getTextField('amazonPayFields', 'storeId', 'adl-amazon-pay-store-id'), + getRadioField('amazonPayFields', 'displayButtonOn', 'adl-amazon-pay-display-button') + ]) + ); + }; + + /** + * Renders the Google Pay config form. + */ + const renderGooglePayForm = () => { + page.append( + ...generator.createFormFields([ + getTextField('googlePayFields', 'gatewayMerchantId'), + getTextField('googlePayFields', 'merchantId'), + getRadioField('googlePayFields', 'displayButtonOn') + ]) + ); + }; + + /** + * Renders the PayPal config form. + */ + const renderPayPalForm = () => { + page.append(...generator.createFormFields([getRadioField('paypalFields', 'displayButtonOn')])); + }; + + /** + * Renders the EPS config form. + */ + const renderEpsForm = () => { + page.append(...generator.createFormFields([getTextField('issuersFields', 'bankIssuer')])); + }; + + /** + * Renders the Oney config form. + */ + const renderOneyForm = () => { + page.append( + ...generator.createFormFields([ + getMultiselectField( + 'oneyFields', + 'supportedInstallments', + [ + { label: 'oneyValues.3x', value: '3' }, + { label: 'oneyValues.4x', value: '4' }, + { label: 'oneyValues.6x', value: '6' }, + { label: 'oneyValues.10x', value: '10' }, + { label: 'oneyValues.12x', value: '12' } + ], + 'adlm--inline', + false, + 'payments.configure.fields.oneyFields.supportedInstallments.placeholder' + ) + ]) + ); + }; + + /** + * Renders the installments config form. + */ + const renderInstallmentsForm = () => { + page.append( + ...generator.createFormFields([ + getRadioField('installmentFields', 'installments'), + getRadioField('installmentFields', 'installmentAmounts'), + getMultiselectField('installmentFields', 'installmentCountries', [ + { label: 'countries.BR', value: 'BR' }, + { label: 'countries.MX', value: 'MX' }, + { label: 'countries.TK', value: 'TK' }, + { label: 'countries.JP', value: 'JP' } + ]), + getNumberField('installmentFields', 'minimumAmount', '', false, 0.01, 0.01), + getTextField('installmentFields', 'numberOfInstallments') + ]) + ); + }; + + /** + * Renders the issuers config form. + */ + const renderIssuersConfigurationForm = () => { + page.append( + ...generator.createFormFields([ + getRadioField('issuersFields', 'showLogos'), + getTextField('issuersFields', 'bankIssuer') + ]) + ); + }; + + /** + * Gets the configuration for the radio input field. + * + * @param {string} type + * @param {string} name + * @param {string} className + * @returns {FormField} + */ + const getRadioField = (type, name, className = '') => { + return { + name, + value: changedMethod.additionalData?.[name] ? '1' : '0', + type: 'radio', + className, + label: `payments.configure.fields.${type}.${name}.label`, + description: `payments.configure.fields.${type}.${name}.description`, + options: [ + { label: 'general.yes', value: '1' }, + { label: 'general.no', value: '0' } + ], + onChange: (value) => handleConfigMethodChange(name, value === '1', true) + }; + }; + + /** + * Gets the configuration for the text input field. + * + * @param {string} type + * @param {string} name + * @param {string} className + * @returns {FormField} + */ + const getTextField = (type, name, className = '') => { + return { + name, + value: changedMethod.additionalData?.[name] || '', + type: 'text', + className, + label: `payments.configure.fields.${type}.${name}.label`, + description: `payments.configure.fields.${type}.${name}.description`, + error: 'validation.requiredField', + onChange: (value) => handleConfigMethodChange(name, value, true) + }; + }; + + /** + * Gets the configuration for the number input field. + * + * @param {string} type + * @param {string} name + * @param {string} className + * @param {boolean?} isInt + * @param {number?} step + * @param {number?} min + * @returns {FormField} + */ + const getNumberField = (type, name, className = '', isInt = true, step = 1, min = 1) => { + return { + name, + value: changedMethod.additionalData?.[name] || '', + type: 'number', + className, + step: step, + min: min, + dataset: { + validationRule: (isInt ? 'integer,' : '') + 'greaterThanZero' + }, + label: `payments.configure.fields.${type}.${name}.label`, + description: `payments.configure.fields.${type}.${name}.description`, + error: 'validation.greaterThanZero', + onChange: (value) => handleConfigMethodChange(name, value, true) + }; + }; + + /** + * Gets the configuration for the multiselect field. + * + * @param {string} type + * @param {string} name + * @param {Option[]} options + * @param {string} className + * @param {boolean} useAny + * @param {string} placeholder + * @returns {FormField} + */ + const getMultiselectField = ( + type, + name, + options, + className = '', + useAny = true, + placeholder = 'general.any' + ) => { + return { + name, + values: changedMethod.additionalData?.[name] || [], + type: 'multiselect', + className, + label: `payments.configure.fields.${type}.${name}.label`, + description: `payments.configure.fields.${type}.${name}.description`, + selectedText: 'general.selectedItems', + placeholder, + options, + useAny, + error: 'validation.requiredField', + onChange: (value) => handleConfigMethodChange(name, value, true) + }; + }; + + /** + * Renders the modal for confirming the deletion of the payment method configuration. + * + * @param {PaymentMethod} method + */ + const renderDeleteModal = (method) => { + const modal = components.Modal.create({ + title: 'payments.delete.title', + content: [generator.createElement('span', '', 'payments.delete.description')], + footer: true, + canClose: true, + buttons: [ + { type: 'secondary', label: 'general.cancel', onClick: () => modal.close() }, + { + type: 'primary', + className: 'adlm--destructive', + label: 'general.delete', + onClick: () => { + deleteMethod(method).finally(() => { + modal.close(); + }); + } + } + ] + }); + + modal.open(); + }; + + /** + * Handles form input field change. + * + * @param {string} prop Changed property name. + * @param {any} value New value. + * @param {boolean?} additional Indicates whether the changed property belongs to the additional config fields. + */ + const handleConfigMethodChange = (prop, value, additional) => { + const areDifferent = (source, target) => { + if (Array.isArray(source) && Array.isArray(target)) { + return !AdyenFE.utilities.compareArrays(source, target); + } + + return source !== target; + }; + + numberOfChanges = 0; + if (additional) { + if (!changedMethod.additionalData) { + changedMethod.additionalData = {}; + } + + changedMethod.additionalData[prop] = value; + } else if (prop === 'logo') { + changedMethod.logoFile = value; + numberOfChanges = 1; + } else { + changedMethod[prop] = value; + } + + Object.entries(changedMethod).forEach(([prop, value]) => { + if (prop === 'additionalData') { + Object.entries(changedMethod.additionalData).forEach(([prop, value]) => { + areDifferent(activeMethod.additionalData[prop], value) && numberOfChanges++; + }); + } else if (!['logoFile', 'isNew', 'paymentType'].includes(prop)) { + areDifferent(activeMethod[prop], value) && numberOfChanges++; + } + }); + + renderFooterState(); + handleDependencies(prop, value, additional); + + if ( + [ + 'name', + 'description', + 'showLogos', + 'singleClickPayment', + 'merchantName', + 'sendBasket', + 'gatewayMerchantId', + 'merchantId', + 'publicKeyId', + 'storeId', + 'installmentAmounts', + 'supportedInstallments' + ].includes(prop) + ) { + validateRequiredField([prop]); + } + + if (prop === 'surchargeType') { + ['fixedSurcharge', 'percentSurcharge', 'surchargeLimit'].forEach((prop) => { + validator.removeError(page.querySelector(`[name="${prop}"]`)); + }); + } + + const field = page.querySelector(`[name="${prop}"]`); + + if (['fixedSurcharge', 'percentSurcharge'].includes(prop)) { + validator.validateNumber(field); + } + + if (prop === 'surchargeLimit') { + if (changedMethod.surchargeType === 'combined') { + field.dataset.validationRule = 'greaterThanZero,greaterThanX|fixedSurcharge'; + } else { + field.dataset.validationRule = 'greaterThanZero'; + } + + validator.validateNumber(field); + } + + if (prop === 'numberOfInstallments') { + validator.validateNumberList(page.querySelector('[name="numberOfInstallments"]'), true, false); + } + }; + + /** + * Handles dependencies change. + * + * @param {string} prop + * @param {string | boolean} value + * @param {boolean} additional + */ + const handleDependencies = (prop, value, additional = false) => { + if (prop === 'surchargeType') { + handleFieldVisibility('fixedSurcharge', value === 'fixed' || value === 'combined'); + handleFieldVisibility('percentSurcharge', value === 'percent' || value === 'combined'); + handleFieldVisibility('surchargeLimit', value === 'percent' || value === 'combined'); + } + + if (additional && prop === 'installments') { + handleFieldVisibility('installmentAmounts', value); + handleFieldVisibility('installmentCountries', value); + handleFieldVisibility('minimumAmount', value); + handleFieldVisibility('numberOfInstallments', value); + } + }; + + /** + * + * @param {string} fieldName + * @param {boolean} condition + */ + const handleFieldVisibility = (fieldName, condition) => { + const field = utilities.getAncestor(page.querySelector(`[name="${fieldName}"]`), 'adl-field-wrapper'); + condition ? utilities.showElement(field) : utilities.hideElement(field); + }; + + /** + * Handles save payment method. + */ + const handleSavePaymentMethod = () => { + utilities.showLoader(); + + // Cannot use utilities.cloneObject here because it will delete uploaded file. + const data = { + ...changedMethod, + additionalData: utilities.cloneObject(changedMethod.additionalData) + }; + const create = data.isNew; + delete data.isNew; + delete data.paymentType; + + if (!isValid()) { + utilities.hideLoader(); + return; + } + + if (data.surchargeType === 'fixed') { + data.percentSurcharge = ''; + } else if (data.surchargeType === 'percent') { + data.fixedSurcharge = ''; + } else if (data.surchargeType === 'none') { + data.percentSurcharge = ''; + data.fixedSurcharge = ''; + data.surchargeLimit = ''; + } + + const postData = new FormData(); + Object.entries(data).forEach(([key, value]) => { + if (key !== 'logoFile' && key !== 'additionalData') { + postData.append(key, value); + } + }); + + if (!data.additionalData?.installments) { + delete data.additionalData.installmentAmounts; + delete data.additionalData.installmentCountries; + delete data.additionalData.numberOfInstallments; + delete data.additionalData.minimumAmount; + } else { + if (!Object.hasOwn(data.additionalData, 'installmentAmounts')) { + data.additionalData.installmentAmounts = false; + } + + if ( + !Object.hasOwn(data.additionalData, 'installmentCountries') || + (Array.isArray(data.additionalData?.installmentCountries) && + data.additionalData.installmentCountries.length === 0) + ) { + data.additionalData.installmentCountries = ['ANY']; + } + + if (!Object.hasOwn(data.additionalData, 'minimumAmount')) { + data.additionalData.minimumAmount = null; + } + } + + postData.append('additionalData', JSON.stringify(data.additionalData || null)); + + if (data.logoFile) { + postData.set('logo', data.logoFile, data.logoFile.name); + } + + const method = create ? 'post' : 'post'; + const url = create + ? configuration.addMethodConfigurationUrl + : configuration.saveMethodConfigurationUrl.replace('{methodId}', data.methodId); + + api[method](url, postData, { + 'Content-Type': 'multipart/form-data' + }) + .then(() => { + utilities.createToasterMessage('payments.configure.method' + (create ? 'Added' : 'Saved')); + navigateToPaymentsForm(); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Validates the configuration form. + * + * @returns {boolean} + */ + const isValid = () => { + const result = [validateRequiredField(['name', 'description']), !page.querySelector(':invalid')]; + + if (['fixed', 'combined'].includes(changedMethod.surchargeType)) { + changedMethod.fixedSurcharge && + result.push(validator.validateNumber(page.querySelector('[name="fixedSurcharge"]'))); + } + + if (['percent', 'combined'].includes(changedMethod.surchargeType)) { + changedMethod.percentSurcharge && + result.push(validator.validateNumber(page.querySelector('[name="percentSurcharge"]'))); + if (changedMethod.surchargeLimit) { + const surchargeLimitField = page.querySelector('[name="surchargeLimit"]'); + if (changedMethod.surchargeType === 'combined') { + surchargeLimitField.dataset.validationRule = 'greaterThanZero,greaterThanX|fixedSurcharge'; + } else { + surchargeLimitField.dataset.validationRule = 'greaterThanZero'; + } + + result.push(validator.validateNumber(surchargeLimitField)); + } + } + + if (changedMethod.additionalData?.installments) { + if (changedMethod.additionalData.minimumAmount) { + result.push(validator.validateNumber(page.querySelector('[name="minimumAmount"]'))); + } + + changedMethod.paymentType === 'creditOrDebitCard' && + result.push( + validator.validateNumberList(page.querySelector('[name="numberOfInstallments"]'), true, false) + ); + } + + if (changedMethod.paymentType === 'creditOrDebitCard') { + result.push( + ...validateRequiredField(['showLogos', 'singleClickPayment', 'sendBasket', 'installmentAmounts']) + ); + } else if (changedMethod.code === 'applepay') { + result.push(...validateRequiredField(['merchantId', 'merchantName'])); + } else if (changedMethod.code === 'amazonpay') { + result.push(...validateRequiredField(['publicKeyId', 'merchantId', 'storeId'])); + } else if (changedMethod.code === 'googlepay' || changedMethod.code === 'paywithgoogle') { + result.push(...validateRequiredField(['gatewayMerchantId', 'merchantId'])); + } else if (changedMethod.code === 'oney') { + result.push(...validateRequiredField(['supportedInstallments'])); + } + + return !result.includes(false); + }; + + /** + * Validates the additional form fields. + * + * @param {(keyof AdditionalDataConfig | 'name' | 'description')[]} fieldNames + * @returns {boolean[]} + */ + const validateRequiredField = (fieldNames) => { + return fieldNames.map((fieldName) => + validator.validateRequiredField(page.querySelector(`[name=${fieldName}]`)) + ); + }; + + /** + * Deletes the payment method configuration. + * + * @param {PaymentMethod} method + */ + const deleteMethod = (method) => { + utilities.showLoader(); + + return api + .delete(configuration.deleteMethodConfigurationUrl.replace('{methodId}', method.methodId)) + .then(() => { + switchPage(renderActivePaymentsForm); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Handles footer visibility state. + */ + const renderFooterState = () => { + utilities.renderFooterState(numberOfChanges, false); + }; + + /** + * Handles switching to payments form. + */ + const navigateToPaymentsForm = () => { + activeMethod = null; + changedMethod = null; + numberOfChanges = 0; + switchPage(renderActivePaymentsForm); + }; + + /** + * Displays page content. + * + * @param {{ storeId: string }} config + */ + this.display = ({ storeId }) => { + configuration.getConfiguredPaymentsUrl = configuration.getConfiguredPaymentsUrl.replace( + '{storeId}', + storeId + ); + configuration.getAvailablePaymentsUrl = configuration.getAvailablePaymentsUrl.replace('{storeId}', storeId); + configuration.getMethodConfigurationUrl = configuration.getMethodConfigurationUrl.replace( + '{storeId}', + storeId + ); + configuration.saveMethodConfigurationUrl = configuration.saveMethodConfigurationUrl.replace( + '{storeId}', + storeId + ); + configuration.addMethodConfigurationUrl = configuration.addMethodConfigurationUrl.replace( + '{storeId}', + storeId + ); + configuration.deleteMethodConfigurationUrl = configuration.deleteMethodConfigurationUrl.replace( + '{storeId}', + storeId + ); + templateService.clearMainPage(); + switchPage(renderActivePaymentsForm); + }; + + /** + * Sets the unsaved changes. + * + * @return {boolean} + */ + this.hasUnsavedChanges = () => { + if (numberOfChanges > 0) { + return true; + } + }; + } + + AdyenFE.PaymentsController = PaymentsController; +})(); diff --git a/Resources/views/backend/_resources/js/ResponseService.js b/Resources/views/backend/_resources/js/ResponseService.js new file mode 100644 index 00000000..92feb75d --- /dev/null +++ b/Resources/views/backend/_resources/js/ResponseService.js @@ -0,0 +1,56 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * The ResponseService constructor. + * + * @constructor + */ + function ResponseService() { + /** + * Handles an error response from the submit action. + * + * @param {{error?: string, errorCode?: string, status?: number}} response + * @returns {Promise} + */ + this.errorHandler = (response) => { + if (response.status !== 401) { + const { utilities, templateService, elementGenerator } = AdyenFE; + let container = document.querySelector('.adlp-flash-message-wrapper'); + if (!container) { + container = elementGenerator.createElement('div', 'adlp-flash-message-wrapper'); + templateService.getMainPage().prepend(container); + } + + templateService.clearComponent(container); + + if (response.error) { + container.prepend(utilities.createFlashMessage(response.error, 'error')); + } else if (response.errorCode) { + container.prepend(utilities.createFlashMessage('general.errors.' + response.errorCode, 'error')); + } else { + container.prepend(utilities.createFlashMessage('general.errors.unknown', 'error')); + } + } + + return Promise.reject(response); + }; + + /** + * Handles 401 response. + * + * @param {{error?: string, errorCode?: string}} response + * @returns {Promise} + */ + this.unauthorizedHandler = (response) => { + AdyenFE.utilities.create401FlashMessage(`general.errors.${response.errorCode}`); + AdyenFE.state.goToState('connection'); + + return Promise.reject({ ...response, status: 401 }); + }; + } + + AdyenFE.responseService = new ResponseService(); +})(); diff --git a/Resources/views/backend/_resources/js/SettingsController.js b/Resources/views/backend/_resources/js/SettingsController.js new file mode 100644 index 00000000..4662492d --- /dev/null +++ b/Resources/views/backend/_resources/js/SettingsController.js @@ -0,0 +1,966 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef GeneralSettings + * @property {boolean} basketItemSync + * @property {'delayed' | 'immediate' | 'manual' | null} capture + * @property {number | null} captureDelay + * @property {string | null} shipmentStatus + * @property {number} retentionPeriod + */ + + /** + * @typedef OrderStatusMappingSettings + * @property {string | null} inProgress + * @property {string | null} pending + * @property {string | null} paid + * @property {string | null} failed + * @property {string | null} refunded + * @property {string | null} cancelled + * @property {string | null} partiallyRefunded + * @property {string | null} new + * @property {string | null} chargeBack + */ + + /** + * @typedef AdyenGivingSettings + * @property {boolean} enableAdyenGiving + * @property {string} charityName + * @property {string} charityDescription + * @property {string} charityMerchantAccount + * @property {string} donationAmount + * @property {string} logo + * @property {Blob?} logoFile + * @property {string} backgroundImage + * @property {Blob?} backgroundImageFile + * @property {string} charityWebsite + */ + + /** + * @typedef SystemInfoSettings + * @property {boolean} debugMode + * @property {{status: boolean, message: string}} webhookValidate + * @property {{status: boolean, message: string}} infoValidate + * @property {string} systemInformation + */ + + /** + * Handles connection page logic. + * + * @param {{ + * getShippingStatusesUrl: string, + * getSettingsUrl: string, + * saveSettingsUrl: string, + * getOrderMappingsUrl: string, + * saveOrderMappingsUrl: string, + * getGivingUrl: string, + * saveGivingUrl: string, + * getSystemInfoUrl: string, + * saveSystemInfoUrl: string, + * webhookValidationUrl: string, + * integrationValidationUrl: string, + * integrationValidationTaskCheckUrl: string, + * downloadWebhookReportUrl: string, + * downloadIntegrationReportUrl: string, + * downloadSystemInfoFileUrl: string, + * page: string}} configuration + * @constructor + */ + function SettingsController(configuration) { + /** @type AjaxServiceType */ + const api = AdyenFE.ajaxService; + + const { templateService, elementGenerator: generator, validationService: validator, utilities } = AdyenFE; + /** @type string */ + let currentStoreId = ''; + /** @type HTMLElement | null */ + let form = null; + + /** @type GeneralSettings | AdyenGivingSettings | OrderStatusMappingSettings */ + let activeSettings; + /** @type GeneralSettings | AdyenGivingSettings | OrderStatusMappingSettings */ + let changedSettings; + /** @type number */ + let numberOfChanges = 0; + + /** @type SystemInfoSettings */ + let systemSettings; + + /** + * Displays page content. + * + * @param {{ state?: string, storeId: string }} config + */ + this.display = ({ storeId }) => { + currentStoreId = storeId; + templateService.clearMainPage(); + [ + 'getShippingStatusesUrl', + 'getSettingsUrl', + 'saveSettingsUrl', + 'getGivingUrl', + 'saveGivingUrl', + 'getOrderMappingsUrl', + 'saveOrderMappingsUrl', + 'webhookValidationUrl', + 'downloadWebhookReportUrl' + ].forEach((prop) => { + configuration[prop] = configuration[prop].replace('{storeId}', storeId); + }); + + return renderPage(); + }; + + /** + * Sets the unsaved changes. + * + * @return {boolean} + */ + this.hasUnsavedChanges = () => false; + + const scrollToTop = () => { + document.querySelector('#adl-page > main')?.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); + }; + + const renderPage = () => { + utilities.showLoader(); + numberOfChanges = 0; + scrollToTop(); + let url; + let renderer; + switch (configuration.page) { + case 'adyen_giving': + url = configuration.getGivingUrl; + renderer = renderAdyenGivingSettingsForm; + break; + case 'order_status_mapping': + url = configuration.getOrderMappingsUrl; + renderer = renderOrderStatusMappingForm; + break; + case 'system_info': + url = configuration.getSystemInfoUrl; + renderer = renderSystemInfoForm; + break; + default: + url = configuration.getSettingsUrl; + renderer = renderGeneralSettingsForm; + break; + } + + return api + .get(url, () => null) + .then(renderer) + .catch(renderer) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Renders the general settings form. + * + * @param {GeneralSettings} settings + */ + const renderGeneralSettingsForm = (settings) => { + activeSettings = utilities.cloneObject(settings); + if (!settings || Object.keys(settings).length === 0) { + /** @type GeneralSettings */ + settings = { + basketItemSync: false, + retentionPeriod: 60, + capture: null, + captureDelay: null, + shipmentStatus: null + }; + + numberOfChanges = 3; + } + + changedSettings = utilities.cloneObject(settings); + + return api + .get(configuration.getShippingStatusesUrl, () => []) + .then( + /** @param {{statusName: string, statusId: string}[]} shippingStatuses */ + (shippingStatuses) => { + renderPageForm( + 'adlp-general-settings', + 'general', + true, + generator.createFormFields( + [ + { + name: 'basketItemSync', + value: settings.basketItemSync, + type: 'checkbox' + }, + { + name: 'capture', + value: settings.capture, + type: 'dropdown', + placeholder: 'settings.general.fields.capture.placeholder', + error: 'settings.general.fields.capture.error', + options: [ + { label: 'settings.general.fields.capture.delayed', value: 'delayed' }, + { label: 'settings.general.fields.capture.immediate', value: 'immediate' }, + { label: 'settings.general.fields.capture.manual', value: 'manual' } + ] + }, + { + name: 'captureDelay', + value: settings.captureDelay, + type: 'number', + className: + 'adlt--capture-delay' + + (settings.capture !== 'delayed' ? ' adls--hidden ' : ''), + step: 1, + dataset: { + validationRule: 'required,integer,minValue|1,maxValue|7' + }, + error: 'settings.general.fields.captureDelay.error' + }, + { + name: 'shipmentStatus', + value: settings.shipmentStatus, + type: 'dropdown', + placeholder: 'settings.general.fields.shipmentStatus.placeholder', + options: shippingStatuses.map((status) => ({ + label: status.statusName, + value: status.statusId + })), + className: + 'adlt--shipment-status' + + (settings.capture !== 'manual' ? ' adls--hidden ' : '') + }, + { + name: 'retentionPeriod', + value: settings.retentionPeriod, + type: 'number', + step: 1, + min: 0, + dataset: { + validationRule: 'required,integer,minValue|60' + }, + error: 'settings.general.fields.retentionPeriod.error' + } + ].map( + /** @param {FormField} config */ + (config) => ({ + ...config, + label: `settings.general.fields.${config.name}.label`, + description: `settings.general.fields.${config.name}.description`, + onChange: (value) => handleChange(config.name, value) + }) + ) + ) + ); + } + ); + }; + + /** + * Renders the order status mappings form. + * + * @param {OrderStatusMappingSettings} mappings + */ + const renderOrderStatusMappingForm = (mappings) => { + if (!mappings || Object.keys(mappings).length === 0) { + /** @type {OrderStatusMappingSettings} */ + mappings = { + inProgress: null, + pending: null, + paid: null, + failed: null, + refunded: null, + partiallyRefunded: null, + cancelled: null, + new: null, + chargeBack: null + }; + } + + activeSettings = utilities.cloneObject(mappings); + changedSettings = utilities.cloneObject(mappings); + + return api + .get(configuration.getShippingStatusesUrl, () => []) + .then( + /** + * @param {{statusName: string, statusId: string}[]} orderStatuses + **/ + (orderStatuses) => { + orderStatuses = [ + { statusId: null, statusName: 'settings.orderStatusMapping.none' }, + ...orderStatuses + ]; + renderPageForm( + 'adlp-order-status-mapping', + 'orderStatusMapping', + true, + generator.createFormFields([ + getDropdownField('inProgress', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField('pending', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField('paid', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField('failed', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField('refunded', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField('partiallyRefunded', mappings, 'orderStatusMapping', orderStatuses), + getDropdownField( + 'cancelled', + mappings, + 'orderStatusMapping', + orderStatuses, + 'adlm--turned' + ), + getDropdownField('new', mappings, 'orderStatusMapping', orderStatuses, 'adlm--turned'), + getDropdownField( + 'chargeBack', + mappings, + 'orderStatusMapping', + orderStatuses, + 'adlm--turned' + ) + ]) + ); + } + ); + }; + + /** + * Gets the default settings for Adyen giving form. + * + * @returns {AdyenGivingSettings} + */ + const getDefaultAdyenGivingSettings = () => ({ + enableAdyenGiving: false, + charityName: '', + logo: '', + logoFile: null, + backgroundImage: '', + backgroundImageFile: null, + charityDescription: '', + charityMerchantAccount: '', + charityWebsite: '', + donationAmount: '' + }); + + /** + * Renders the adyen giving settings form. + * + * @param {AdyenGivingSettings} adyenGiving + */ + const renderAdyenGivingSettingsForm = (adyenGiving) => { + activeSettings = utilities.cloneObject(adyenGiving); + + if (!adyenGiving || Object.keys(adyenGiving).length === 0) { + /** @type AdyenGivingSettings */ + adyenGiving = getDefaultAdyenGivingSettings(); + + numberOfChanges = 3; + } + + changedSettings = utilities.cloneObject(adyenGiving); + renderPageForm( + 'adlp-adyen-giving-settings', + 'adyenGiving', + true, + generator.createFormFields([ + { + name: 'enableAdyenGiving', + value: changedSettings.enableAdyenGiving || false, + type: 'checkbox', + label: `settings.adyenGiving.fields.enableAdyenGiving.label`, + description: `settings.adyenGiving.fields.enableAdyenGiving.description`, + onChange: (value) => handleChange('enableAdyenGiving', value) + }, + getTextField('charityName', 'adyenGiving'), + getTextField('charityDescription', 'adyenGiving'), + getTextField('charityMerchantAccount', 'adyenGiving'), + getTextField('donationAmount', 'adyenGiving'), + getTextField('charityWebsite', 'adyenGiving'), + getFileUploadField('backgroundImage', 'adyenGiving'), + getFileUploadField('logo', 'adyenGiving') + ]) + ); + + handleDependencies('enableAdyenGiving', adyenGiving.enableAdyenGiving); + }; + + /** + * Renders settings form. + * + * @param {string} className + * @param {string} page + * @param {boolean?} useFooter + * @param {HTMLElement[]} formFields + */ + const renderPageForm = (className, page, useFooter, formFields) => { + if (form) { + templateService.clearComponent(form); + } + + form = generator.createElement('div', className, '', null, [ + generator.createElement('div', 'adlp-flash-message-wrapper'), + generator.createElement('h2', '', `settings.${page}.title`), + generator.createElement('p', '', `settings.${page}.description`), + ...formFields, + useFooter + ? generator.createFormFooter( + handleSave, + () => { + if (numberOfChanges > 0) { + return renderPage(); + } else { + scrollToTop(); + } + }, + 'general.discardChanges' + ) + : '' + ]); + + templateService.getMainPage().append(form); + if (useFooter) { + renderFooterState(numberOfChanges); + } + }; + + /** + * Renders the system info settings form. + * + * @param {SystemInfoSettings} systemInfoSettings + */ + const renderSystemInfoForm = (systemInfoSettings) => { + systemSettings = { ...systemInfoSettings }; + + renderPageForm( + 'adlp-system-info-settings', + 'system', + false, + generator.createFormFields([ + { + name: 'debugMode', + value: systemInfoSettings.debugMode, + type: 'checkbox', + label: `settings.system.fields.debugMode.label`, + description: `settings.system.fields.debugMode.description`, + onChange: (value) => handleDebugMode(!!value) + }, + getButtonField('adyenWebhooksValidation', 'adlp-setting-field', 'validate', handleValidateWebhooks), + getButtonField( + 'integrationConfigurationValidation', + 'adlp-setting-field', + 'validate', + performIntegrationValidation + ), + getButtonLinkField( + 'downloadSystemInformation', + 'adlp-setting-field adlp-download-report', + 'downloadReport', + configuration.downloadSystemInfoFileUrl + ), + getButtonLinkField( + 'contactAdyenSupport', + 'adlp-setting-field adlp-contact-adyen-support', + 'contactAdyenSupport', + 'https://www.adyen.help/hc/en-us' + ) + ]) + ); + }; + + /** + * Handles form input field change. + * + * @param {keyof (GeneralSettings & OrderStatusMappingSettings & AdyenGivingSettings & SystemInfoSettings)} prop + * @param {any} value + */ + const handleChange = (prop, value) => { + if (['captureDelay', 'retentionPeriod'].includes(prop)) { + changedSettings[prop] = Number(value); + } else { + changedSettings[prop] = value; + } + + if (prop === 'capture') { + handleOnCaptureChange(value); + } + + if (prop === 'logo') { + changedSettings.logoFile = value; + } + + if (prop === 'backgroundImage') { + changedSettings.backgroundImageFile = value; + } + + validator.removeError(form.querySelector('[name="' + prop + '"]')); + + if (!configuration.page) { + validateGeneralSettings(prop); + } else if (configuration.page === 'adyen_giving') { + validateAdyenGivingSettings(prop); + } + + renderFooterState(); + handleDependencies(prop, value); + }; + + /** + * Handles capture field change. + * + * @param {'delayed' | 'immediate' | 'manual'} value + */ + const handleOnCaptureChange = (value) => { + utilities.hideElement(form.querySelector('.adlt--capture-delay')); + utilities.hideElement(form.querySelector('.adlt--shipment-status')); + switch (value) { + case 'delayed': + utilities.showElement(form.querySelector('.adlt--capture-delay')); + break; + case 'manual': + utilities.showElement(form.querySelector('.adlt--shipment-status')); + break; + } + }; + + /** + * Handles dependencies change. + * + * @param {string} prop + * @param {string | boolean} value + */ + const handleDependencies = (prop, value) => { + if (prop === 'enableAdyenGiving') { + handleFieldVisibility('charityName', value); + handleFieldVisibility('charityDescription', value); + handleFieldVisibility('charityMerchantAccount', value); + handleFieldVisibility('donationAmount', value); + handleFieldVisibility('charityWebsite', value); + handleFieldVisibility('backgroundImage', value); + handleFieldVisibility('logo', value); + } + }; + + /** + * Handles field visibility. + * + * @param {string} fieldName + * @param {boolean} condition + */ + const handleFieldVisibility = (fieldName, condition) => { + const field = utilities.getAncestor(form.querySelector(`[name="${fieldName}"]`), 'adl-field-wrapper'); + condition ? utilities.showElement(field) : utilities.hideElement(field); + }; + + /** + * Gets the configuration for the dropdown field. + * + * @param {string} name + * @param {GeneralSettings | OrderStatusMappingSettings | AdyenGivingSettings} settingsTypeObject + * @param {string} settingsTypeName + * @param {string?} className + * @param {{statusName: string, statusId: string}[]} options + * @returns {FormField} + */ + const getDropdownField = (name, settingsTypeObject, settingsTypeName, options, className = '') => { + return { + name, + value: settingsTypeObject?.[name] || null, + type: 'dropdown', + className, + placeholder: `settings.${settingsTypeName}.fields.${name}.placeholder`, + label: `settings.${settingsTypeName}.fields.${name}.label`, + description: `settings.${settingsTypeName}.fields.${name}.description`, + options: options.map((status) => ({ + label: status.statusName, + value: status.statusId + })), + onChange: (value) => handleChange(name, value) + }; + }; + + /** + * Gets the configuration for the file upload field. + * + * @param {string} name + * @param {string} settingsTypeName + * @returns {FormField} + */ + const getFileUploadField = (name, settingsTypeName) => { + return { + name, + value: changedSettings?.[name] || '', + type: 'file', + supportedMimeTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml', 'image/svg'], + label: `settings.${settingsTypeName}.fields.${name}.label`, + description: `settings.${settingsTypeName}.fields.${name}.description`, + placeholder: `settings.${settingsTypeName}.fields.${name}.placeholder`, + error: 'validation.requiredField', + onChange: (value) => handleChange(name, value) + }; + }; + + /** + * Gets the configuration for the text input field. + * + * @param {string} name + * @param {string} settingsTypeName + * @returns {FormField} + */ + const getTextField = (name, settingsTypeName) => { + return { + name, + value: changedSettings?.[name] || '', + type: 'text', + label: `settings.${settingsTypeName}.fields.${name}.label`, + description: `settings.${settingsTypeName}.fields.${name}.description`, + error: 'validation.requiredField', + onChange: (value) => handleChange(name, value) + }; + }; + + /** + * Gets the configuration for the button field. + * + * @param {string} name + * @param {string?} className + * @param {string} buttonText + * @param {() => void?} onClick + */ + const getButtonField = (name, className, buttonText, onClick) => { + return { + name: name, + className: className, + value: '', + type: 'button', + buttonType: 'secondary', + buttonSize: 'medium', + buttonLabel: `settings.system.buttons.` + buttonText, + label: `settings.system.fields.${name}.label`, + description: `settings.system.fields.${name}.description`, + onClick: onClick + }; + }; + + /** + * Gets the configuration for the link (that looks like a button) field. + * + * @param {string} name + * @param {string?} className + * @param {string} text + * @param {string} href + * @param {boolean?} useDownload + * @param {string?} downloadFile + */ + const getButtonLinkField = (name, className, text, href, useDownload, downloadFile) => { + return { + name: name, + className: className, + value: '', + type: 'buttonLink', + text: `settings.system.buttons.` + text, + href: href, + useDownload: useDownload, + downloadFile: downloadFile, + label: `settings.system.fields.${name}.label`, + description: `settings.system.fields.${name}.description` + }; + }; + + /** + * Handles footer visibility state. + * + * @param {number} changes + */ + const renderFooterState = (changes = 0) => { + numberOfChanges = changes; + Object.entries(changedSettings).forEach(([prop, value]) => { + if (!['logoFile', 'backgroundImageFile'].includes(prop) && activeSettings[prop] !== value) { + numberOfChanges++; + } + }); + + if (changedSettings.logoFile) { + numberOfChanges++; + } + + if (changedSettings.backgroundImageFile) { + numberOfChanges++; + } + + utilities.renderFooterState(numberOfChanges); + }; + + /** + * Handles debug mode change. + * + * @param {boolean} value + */ + const handleDebugMode = (value) => { + utilities.showLoader(); + + let data = { debugMode: value }; + + api.post(configuration.saveSystemInfoUrl, data) + .then(renderPage) + .then(() => { + utilities.createToasterMessage('settings.system.debugMode' + (value ? 'Enabled' : 'Disabled')); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + const performIntegrationValidation = () => { + let url; + + const doCall = () => { + api.get(url, (e) => { + throw e; + }) + .then((response) => { + if (response?.finished) { + showMessage( + [ + `settings.system.messages.${ + response?.status + ? 'successIntegrationValidation' + : 'failedIntegrationValidation' + }`, + 'settings.system.messages.downloadReportText|' + + configuration.downloadIntegrationReportUrl + ], + response?.status ? 'success' : 'error' + ); + + utilities.hideLoader(); + } else { + setTimeout(doCall, 500); + } + }) + .catch((reason) => { + showMessage(`general.errors.${reason?.errorCode || 'unknown'}`, 'error'); + utilities.hideLoader(); + }); + }; + + utilities.showLoader(); + api.post(configuration.integrationValidationUrl).then( + /** @param {{queueItemId: string}} response */ + (response) => { + url = configuration.integrationValidationTaskCheckUrl.replace( + '{queueItemId}', + response.queueItemId + ); + + setTimeout(doCall, 500); + } + ); + }; + + /** + * Handles webhooks and integration configuration validation. + */ + const handleValidateWebhooks = () => { + utilities.showLoader(); + + api.post(configuration.webhookValidationUrl, null, null, (response) => response) + .then((response) => { + showMessage( + [ + 'settings.system.messages.' + + (response?.status ? 'success' : 'failed') + + 'WebhookValidation', + 'settings.system.messages.downloadReportText|' + configuration.downloadWebhookReportUrl + ], + response?.status ? 'success' : 'error' + ); + }) + .catch(() => false) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Saves the current page. + */ + const handleSave = () => { + utilities.showLoader(); + + if (!configuration.page && !validateGeneralSettings()) { + utilities.hideLoader(); + return; + } + + if (configuration.page === 'adyen_giving' && !validateAdyenGivingSettings()) { + utilities.hideLoader(); + return; + } + + let data = { ...changedSettings }; + + let promise; + if (configuration.page === 'adyen_giving') { + if (!data.enableAdyenGiving) { + data = getDefaultAdyenGivingSettings(); + } + + const postData = new FormData(); + Object.entries(data).forEach(([key, value]) => { + if (!['logo', 'logoFile', 'backgroundImage', 'backgroundImageFile'].includes(key)) { + postData.append(key, value); + } + }); + + if (data.logoFile) { + postData.set('logo', data.logoFile, data.logoFile.name); + } + + if (data.backgroundImageFile) { + postData.set('backgroundImage', data.backgroundImageFile, data.backgroundImageFile.name); + } + + promise = api.post(configuration.saveGivingUrl, postData, { + 'Content-Type': 'multipart/form-data' + }); + } else if (configuration.page === 'order_status_mapping') { + promise = api.post(configuration.saveOrderMappingsUrl, data); + } else { + if (data.capture !== 'delayed') { + data.captureDelay = null; + } + promise = api.post(configuration.saveSettingsUrl, data); + } + + promise + .then(renderPage) + .then(showMessage) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Validates the general settings form. + * + * @param {keyof GeneralSettings?} prop Indicates a field to be validated. + * @returns {boolean} TRUE if all the fields are valid. + */ + const validateGeneralSettings = (prop) => { + const captureInput = form.querySelector('[name="capture"]'); + + const result = []; + if (!prop || prop === 'capture') { + result.push(validator.validateRequiredField(captureInput)); + } + + if (!prop || prop === 'captureDelay') { + result.push( + captureInput.value !== 'delayed' || + validator.validateNumber(form.querySelector('[name="captureDelay"]')) + ); + } + + if (!prop || prop === 'shipmentStatus') { + result.push( + captureInput.value !== 'manual' || + validator.validateRequiredField(form.querySelector('[name="shipmentStatus"]')) + ); + } + + if (!prop || prop === 'retentionPeriod') { + result.push(validator.validateNumber(form.querySelector('[name="retentionPeriod"]'))); + } + + if (!prop) { + result.push(!form.querySelector(':invalid')); + } + + return !result.includes(false); + }; + + /** + * Validates Adyen giving form. + * + * @param {keyof AdyenGivingSettings?} prop Indicates a field to be validated. + * @returns {boolean} TRUE if all the fields are valid. + */ + const validateAdyenGivingSettings = (prop) => { + if (changedSettings.enableAdyenGiving) { + const fields = prop + ? [prop] + : [ + 'charityName', + 'logo', + 'backgroundImage', + 'charityDescription', + 'charityMerchantAccount', + 'charityWebsite', + 'donationAmount' + ]; + + const result = []; + fields.forEach((fieldName) => { + const field = form.querySelector(`[name=${fieldName}]`); + if (['charityName', 'charityDescription', 'charityMerchantAccount'].includes(fieldName)) { + result.push(validator.validateRequiredField(field)); + } else if (fieldName === 'charityWebsite') { + result.push(validateUrlField(fieldName, false)); + } else if (['logo', 'backgroundImage'].includes(fieldName)) { + if (!changedSettings[fieldName] && !changedSettings[fieldName + 'File']) { + validator.setError(field); + result.push(false); + } + } + + if (fieldName === 'donationAmount') { + result.push(validator.validateNumberList(field)); + } + }); + + return !result.includes(false); + } + + return true; + }; + + /** + * Validates if the field is a valid URL. Additionally, validates if the field is set, if it is mandatory. + * @param {string} name The name of the field. + * @param {boolean} mandatory Indicates whether to validate a required field. + * @return {boolean} TRUE if the field is a valid URL. + */ + const validateUrlField = (name, mandatory) => { + if (changedSettings[name]) { + return validator.validateUrl(form.querySelector(`[name="${name}"]`), 'validation.invalidUrl'); + } + + return mandatory + ? validator.validateRequiredField(form.querySelector(`[name="${name}"]`), 'validation.requiredField') + : true; + }; + + /** + * Displays the success flash message. + * + * @param {string|string[]} message + * @param {'success'|'error'} type + */ + const showMessage = (message = 'general.changesSaved', type = 'success') => { + const container = form?.querySelector('.adlp-flash-message-wrapper'); + if (!container) { + return; + } + + templateService.clearComponent(container); + container.append(utilities.createFlashMessage(message, type)); + scrollToTop(); + }; + } + + AdyenFE.SettingsController = SettingsController; +})(); diff --git a/Resources/views/backend/_resources/js/StateController.js b/Resources/views/backend/_resources/js/StateController.js new file mode 100644 index 00000000..d0b8845c --- /dev/null +++ b/Resources/views/backend/_resources/js/StateController.js @@ -0,0 +1,490 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef Store + * @property {string} storeId + * @property {string} storeName + * @property {boolean} maintenanceMode + */ + /** + * @typedef Merchant + * @property {string} merchantId + * @property {string} merchantName + */ + + /** + * @typedef StateConfiguration + * @property {string?} pagePlaceholder + * @property {string} stateUrl + * @property {string} storesUrl + * @property {string} currentStoreUrl + * @property {string} connectionDetailsUrl + * @property {string} merchantsUrl + * @property {string} versionUrl + * @property {string} downloadVersionUrl + * @property {string?} systemId + * @property {Record} pageConfiguration + * @property {Record} templates + */ + + /** + * Main controller of the application. + * + * @param {StateConfiguration} configuration + * + * @constructor + */ + function StateController(configuration) { + /** @type AjaxServiceType */ + const api = AdyenFE.ajaxService; + + const { pageControllerFactory, utilities, templateService, elementGenerator, translationService } = AdyenFE; + + let currentState = ''; + let previousState = ''; + let controller = null; + + /** + * Main entry point for the application. + * Initializes the sidebar. + * Determines the current state and runs the start controller. + */ + this.display = () => { + utilities.showLoader(); + templateService.setTemplates(configuration.templates); + templateService.clearMainPage(); + + window.addEventListener('hashchange', updateStateOnHashChange, false); + + api.get(!getStoreId() ? configuration.currentStoreUrl : configuration.storesUrl, () => null, true) + .then( + /** @param {Store|Store[]} response */ + (response) => { + const loadStore = (store) => { + setStoreId(store.storeId); + setMaintenanceMode(store.maintenanceMode); + + return Promise.all([displayPageBasedOnState(), this.setHeader()]); + }; + + let store; + if (!Array.isArray(response)) { + store = response; + } else { + store = response.find((s) => s.storeId === getStoreId()); + } + + if (!store) { + // the active store is probably deleted, we need to switch to the default store + return api.get(configuration.currentStoreUrl, null, true).then(loadStore); + } + + return loadStore(store); + } + ) + .catch(() => { + initializeSidebar(); + this.disableSidebar(); + return this.setHeader(); + }) + .finally(() => { + utilities.hideLoader(); + }); + }; + + /** + * Navigates to a state. + * + * @param {string} state + * @param {Record | null?} additionalConfig + * @param {boolean} [force=false] + */ + this.goToState = (state, additionalConfig = null, force = false) => { + if (currentState === state && !force) { + return; + } + + window.location.hash = state; + + const config = { + storeId: getStoreId(), + ...(additionalConfig || {}) + }; + + const sidebar = document.querySelector('#adl-page .adl-sidebar'); + if (!window.location.hash.startsWith('#settings')) { + sidebar && utilities.hideElement(sidebar.querySelector('.adlp-quick-links')); + } else { + sidebar && utilities.showElement(sidebar.querySelector('.adlp-quick-links')); + } + + const [controllerName, page, stateParam] = state.split('-'); + controller = pageControllerFactory.getInstance( + controllerName, + getControllerConfiguration(controllerName, page, stateParam) + ); + + if (controller) { + controller.display(config); + } + + previousState = currentState; + currentState = state; + }; + + /** + * Enables the sidebar. + */ + this.enableSidebar = () => { + document + .querySelectorAll('.adl-sidebar .adlp-menu-item') + .forEach((item) => item.classList.remove('adls--disabled')); + }; + + /** + * Disables the sidebar. + */ + this.disableSidebar = () => { + document + .querySelectorAll('.adl-sidebar .adlp-menu-item:not(.adlt--connection)') + .forEach((item) => item.classList.add('adls--disabled')); + }; + + /** + * Updates the main header. + * + * @returns {Promise} + */ + this.setHeader = () => { + return Promise.all([setStoresSwitcher(), setConnectionData()]); + }; + + const updateStateOnHashChange = () => { + const state = window.location.hash.substring(1); + if (state) { + this.goToState(state); + updateSidebarState(); + } + + getSidebar().classList.remove('adls--menu-active'); + }; + + /** + * Selects active sidebar item based on the location hash. + */ + const updateSidebarState = () => { + const sidebar = getSidebar(); + sidebar.querySelectorAll('.adlp-menu-item a').forEach((el) => el.classList.remove('adls--active')); + sidebar.querySelector(`[href="${location.hash}"]`)?.classList.add('adls--active'); + }; + + /** + * Gets the sidebar DOM element. + * + * @returns {HTMLElement} + */ + const getSidebar = () => { + return document.querySelector('#adl-page .adl-sidebar'); + }; + + /** + * Handles version download. + * + * @param {string} latest + */ + const setDownloadVersion = (latest) => { + if (configuration.downloadVersionUrl) { + const downloadVersionBox = document.querySelector( + '#adl-page .adl-header-navigation .adlp-nav-list .adlp-nav-item.adlm--download' + ); + const title = translationService.translate('mainHeader.download'); + + downloadVersionBox.classList.remove('adls--hidden'); + templateService.clearComponent(downloadVersionBox); + downloadVersionBox.append( + elementGenerator.createElement( + 'a', + 'adlp-download-link', + '', + { href: configuration.downloadVersionUrl, target: '_blank' }, + [ + elementGenerator.createElement('span', 'adlp-nav-item-icon adlm--download'), + elementGenerator.createElement('div', 'adlp-nav-item-text', '', null, [ + elementGenerator.createElement('h3', 'adlp-nav-item-title', title), + elementGenerator.createElement('span', 'adlp-nav-item-subtitle', latest) + ]) + ] + ) + ); + } + }; + + /** + * Renders a confirmation modal for a store change when there are unsaved changes. + */ + const renderSwitchToStoreModal = () => { + return new Promise((resolve) => { + const modal = AdyenFE.components.Modal.create({ + title: 'payments.switchToStore.title', + content: [AdyenFE.elementGenerator.createElement('span', '', 'payments.switchToStore.description')], + footer: true, + canClose: false, + buttons: [ + { + type: 'secondary', + label: 'general.back', + onClick: () => { + modal.close(); + resolve(false); + } + }, + { + type: 'primary', + className: 'adlt--primary', + label: 'general.yes', + onClick: () => { + modal.close(); + resolve(true); + } + } + ] + }); + + modal.open(); + }); + }; + + /** + * Sets the store switcher in the main header. + */ + const setStoresSwitcher = () => { + return api.get(configuration.storesUrl, null, true).then( + /** @param {Store[]} stores */ + (stores) => { + if (!stores?.length) { + return; + } + + const storesData = stores.map((store) => ({ + label: store.storeName, + value: store.storeId + })); + const elem = document.getElementById('adl-store-switcher'); + templateService.clearComponent(elem); + elem.append( + elementGenerator.createStoreSwitcher( + storesData, + '', + 'mainHeader.switchStore', + getStoreId(), + () => { + if (controller !== null && controller.hasUnsavedChanges()) { + return renderSwitchToStoreModal(); + } + + return Promise.resolve(true); + }, + (storeId) => { + setStoreId(storeId); + window.location.hash = ''; + this.enableSidebar(); + this.display(); + } + ) + ); + elem.classList.remove('adls--hidden'); + } + ); + }; + + /** + * Updated the connection data in the main header. + * + * @returns {Promise} + */ + const setConnectionData = () => { + return api + .get(configuration.connectionDetailsUrl.replace('{storeId}', getStoreId()), () => null, true) + .then( + /** @param {Connection} connection */ + (connection) => { + const modeElem = document.querySelector('.adlp-nav-item.adlm--mode'); + const merchantElem = document.querySelector('.adlp-nav-item.adlm--merchant'); + const data = connection?.mode === 'test' ? connection?.testData : connection?.liveData; + if (!data?.apiKey) { + modeElem.classList.add('adls--hidden'); + merchantElem.classList.add('adls--hidden'); + return; + } + + templateService.clearComponent(modeElem); + modeElem.classList.remove('adls--hidden'); + modeElem.append( + ...elementGenerator.createHeaderItem( + 'mainHeader.mode', + 'connection.environment.options.' + connection.mode + ) + ); + + return api + .get(configuration.merchantsUrl.replace('{storeId}', getStoreId()), () => [], true) + .then( + /** @param {Merchant[]} merchants} */ + (merchants) => { + const merchantName = merchants.find( + (m) => m.merchantId === data.merchantId + )?.merchantName; + templateService.clearComponent(merchantElem); + if (!merchantName) { + merchantElem.classList.add('adls--hidden'); + } else { + merchantElem.classList.remove('adls--hidden'); + } + + merchantElem.append( + ...elementGenerator.createHeaderItem('mainHeader.merchant', merchantName) + ); + } + ); + } + ); + }; + + /** + * Display the maintenance mode message. + * + * @param {boolean} enabled If a message should be displayed. + */ + const setMaintenanceMode = (enabled) => { + const container = templateService.getHeaderSection(); + templateService.clearComponent(container); + enabled && + container.append( + elementGenerator.createFlashMessage(['maintenance.title', 'maintenance.description'], 'warning') + ); + }; + + /** + * Initializes the sidebar. + */ + const initializeSidebar = () => { + const sidebar = getSidebar(); + + if (sidebar) { + sidebar.innerHTML = templateService.getTemplate('sidebar'); + const versionInfo = sidebar.querySelector('.adlp-version-info'); + versionInfo.classList.add('adls--hidden'); + + // initialize version + api.get(configuration.versionUrl, null, true) + .then((version) => { + versionInfo.classList.remove('adls--hidden'); + versionInfo.innerHTML = version.installed; + + if (version.installed !== version.latest) { + versionInfo.classList.add('adls--warning'); + versionInfo.classList.add('adl-hint'); + versionInfo.append( + elementGenerator.createElement( + 'span', + 'adlp-tooltip adlt--bottom', + 'sidebar.newVersionAvailable' + ) + ); + setDownloadVersion(version.latest); + } + }) + .catch(() => { + // probably not implemented yet + sidebar.querySelector('.adlp-version-info').remove(); + }); + + sidebar.querySelector('.adlp-mobile-menu').addEventListener('click', () => { + sidebar.classList.toggle('adls--menu-active'); + }); + sidebar.querySelector('.adlp-mobile-underlay').addEventListener('click', () => { + sidebar.classList.remove('adls--menu-active'); + }); + updateSidebarState(); + } + }; + + /** + * Returns the current merchant state. + * + * @return {Promise<"onboarding" | "dashboard">} + */ + this.getCurrentMerchantState = () => { + return api + .get(configuration.stateUrl.replace('{storeId}', getStoreId()), () => {}) + .then((response) => response?.state || 'onboarding'); + }; + + /** + * Opens a specific page based on the current state. + */ + const displayPageBasedOnState = () => { + initializeSidebar(); + return this.getCurrentMerchantState().then((state) => { + // if user is logged in, go to payments + switch (state) { + case 'onboarding': + this.disableSidebar(); + api.get(configuration.connectionDetailsUrl.replace('{storeId}', getStoreId()), () => null).then( + (connection) => { + if (connection?.testData?.apiKey || connection?.liveData?.apiKey) { + this.goToState('connection-merchant', null, true); + } else { + this.goToState('connection', null, true); + } + } + ); + break; + default: + this.goToState(window.location.hash.substring(1) || 'payments', null, true); + break; + } + }); + }; + + /** + * Gets controller configuration. + * + * @param {string} controllerName + * @param {string?} page + * @param {string?} stateParam + * @return {Record}} + */ + const getControllerConfiguration = (controllerName, page, stateParam) => { + let config = utilities.cloneObject(configuration.pageConfiguration[controllerName] || {}); + + page && (config.page = page); + stateParam && (config.stateParam = stateParam); + + return config; + }; + + /** + * Sets the store ID. + * + * @param {string} storeId + */ + const setStoreId = (storeId) => { + sessionStorage.setItem('adl-active-store-id', storeId); + }; + + /** + * Gets the store ID. + * + * @returns {string} + */ + const getStoreId = () => { + return sessionStorage.getItem('adl-active-store-id'); + }; + } + + AdyenFE.StateController = StateController; +})(); diff --git a/Resources/views/backend/_resources/js/TableFilterComponent.js b/Resources/views/backend/_resources/js/TableFilterComponent.js new file mode 100644 index 00000000..93e3a59a --- /dev/null +++ b/Resources/views/backend/_resources/js/TableFilterComponent.js @@ -0,0 +1,320 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +if (!window.AdyenFE.components) { + window.AdyenFE.components = {}; +} + +(function () { + /** + * @typedef TableFilterParams + * + * @property {Option[]} options + * @property {string?} name + * @property {string[]?} values + * @property {(values: string[]) => void} onChange + * @property {string?} label + * @property {string?} selectPlaceholder + * @property {boolean?} isMultiselect + */ + const { elementGenerator: generator, components } = AdyenFE; + + const preventDefaults = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + /** + * Compares contents of two arrays. + * + * @param {string[]} a1 + * @param {string[]} a2 + * @return {boolean} + */ + const arraysHaveSameContent = (a1, a2) => { + if (a1.length !== a2.length) { + return false; + } + + for (let i = 0; i < a1.length; i++) { + if (!a2.includes(a1[i])) { + return false; + } + } + + for (let i = 0; i < a2.length; i++) { + if (!a1.includes(a2[i])) { + return false; + } + } + + return true; + }; + + /** + * Gets the label to be displayed in the main button. + * + * @param {string} label The default label when nothing is selected. + * @param {string} labelPlural Label when more than two options are selected. + * @param {Option[]} options Possible options. + * @param {string[]} values Selected values. + * @return {string} + */ + const getButtonLabel = (label, labelPlural, options, values) => { + if (values.length === 0) { + return label; + } + + if (values.length < 2) { + return values.map((value) => options.find((o) => o.value === value).label).join(', '); + } + + return `${values.length} ${labelPlural.toLowerCase()}`; + }; + + /** + * Gets the label to be displayed in the main button. + * + * @param {string} label The default label when nothing is selected. + * @param {Option[]} options Possible options. + * @param {string[]} values Selected values. + * @return {string} + */ + const getButtonTooltip = (label, options, values) => { + if (values.length === 0) { + return ''; + } + + if (values.length < 2) { + return label; + } + + return values.map((value) => options.find((o) => o.value === value).label).join(', '); + }; + + /** + * Renders the main button. + * + * @param {string} label + * @param {string} labelPlural + * @param {Option[]} options + * @param {string[]} values + * @param {() => void} onClick + * @param {() => void} onClear + * @return {HTMLButtonElement} + */ + const renderButton = (label, labelPlural, options, values, onClick, onClear) => { + const button = generator.createButton({ + type: 'secondary', + className: 'adlp-filter-button' + (values.length > 0 ? ' adls--selected' : ''), + label: getButtonLabel(label, labelPlural, options, values), + onClick: onClick + }); + + const deleteButton = generator.createElement('button', 'adlp-delete-text-button'); + deleteButton.addEventListener('click', (e) => { + preventDefaults(e); + onClear(); + }); + + button.append( + deleteButton, + generator.createElement('span', 'adlp-tooltip', getButtonTooltip(label, options, values)) + ); + + return button; + }; + + /** + * Renders selected options. + * + * @param {Option[]} options + * @param {string[]} selectedValues + * @param {(value: string) =>, void} onRemove + * @return {HTMLElement[]} + */ + const getOptionsList = (options, selectedValues, onRemove) => { + return selectedValues.map((value) => { + const deleteButton = generator.createElementFromHTML(''); + deleteButton.addEventListener('click', (e) => { + preventDefaults(e); + onRemove(value); + }); + + const element = generator.createElement( + 'li', + 'adlp-selected-data-item', + options.find((o) => o.value === value).label, + null + ); + + element.prepend(deleteButton); + + return element; + }); + }; + + /** + * Creates a table filter element. + * + * @param {TableFilterParams} args + * @return {HTMLElement} + */ + const create = ({ + options, + name = '', + values = [], + onChange, + label = '', + labelPlural = '', + selectPlaceholder = '', + isMultiselect = true + }) => { + let selectedValues = [...values]; + + const createDropdown = () => + components.Dropdown.create({ + options, + name, + placeholder: selectPlaceholder, + onChange: handleSelectChange, + value: isMultiselect ? undefined : selectedValues[0], + updateTextOnChange: !isMultiselect, + searchable: true + }); + + const createFilterContainerContent = () => { + dataContainer.append( + ...[ + generator.createElement('span', 'adlp-data-label', label), + createDropdown(), + generator.createElement( + 'ul', + 'adlp-selected-data', + '', + null, + isMultiselect + ? getOptionsList(options, selectedValues, (value) => handleSelectChange(value, false)) + : [] + ) + ] + ); + + clearButton.disabled = selectedValues.length === 0; + applyButton.disabled = arraysHaveSameContent(selectedValues, values); + }; + + const fireOnChange = (values) => { + selectedValues = values; + handleSelectedValuesChange(); + filterContainer.classList.remove('adls--open'); + values.length ? button.classList.add('adls--selected') : button.classList.remove('adls--selected'); + button.firstElementChild.innerHTML = getButtonLabel(label, labelPlural, options, selectedValues); + button.lastElementChild.innerHTML = getButtonTooltip(label, options, selectedValues); + dataContainer.innerHTML = ''; + onChange?.(selectedValues); + }; + + const handleSelectedValuesChange = () => { + const list = filterContainer.querySelector('.adlp-selected-data'); + if (list && isMultiselect) { + list.innerHTML = ''; + list.append(...getOptionsList(options, selectedValues, (value) => handleSelectChange(value, false))); + } else if (!isMultiselect && selectedValues.length === 0) { + // reset value for the dropdown + const previousDD = filterContainer.querySelector('.adl-single-select-dropdown'); + dataContainer.insertBefore(createDropdown(), previousDD); + + previousDD?.remove(); + } + + clearButton.disabled = selectedValues.length === 0; + applyButton.disabled = arraysHaveSameContent(selectedValues, values); + }; + + const handleSelectChange = (value, add = true) => { + if (add) { + isMultiselect && !selectedValues.includes(value) && selectedValues.push(value); + !isMultiselect && (selectedValues = [value]); + } else if (isMultiselect) { + selectedValues = selectedValues.filter((v) => v !== value); + } else { + selectedValues = []; + } + + handleSelectedValuesChange(); + }; + + const closeFilter = () => { + selectedValues = [...values]; + filterContainer.classList.remove('adls--open'); + dataContainer.innerHTML = ''; + }; + + const button = renderButton( + label, + labelPlural, + options, + values, + () => { + if (filterContainer.classList.contains('adls--open')) { + dataContainer.innerHTML = ''; + } else { + createFilterContainerContent(); + } + + filterContainer.classList.toggle('adls--open'); + }, + () => { + fireOnChange([]); + } + ); + + const clearButton = generator.createButton({ + type: 'secondary', + size: 'small', + label: 'general.clear', + className: 'adlm--blue', + disabled: values.length === 0, + onClick: () => { + selectedValues = []; + handleSelectedValuesChange(); + } + }); + const applyButton = generator.createButton({ + type: 'primary', + size: 'small', + label: 'general.apply', + className: 'adlm--blue', + disabled: true, + onClick: () => fireOnChange(selectedValues) + }); + + const dataContainer = generator.createElement('div', 'adlp-dropdown-data'); + const filterContainer = generator.createElement('div', 'adlp-dropdown-container', '', null, [ + generator.createElement('div', 'adlp-content', '', null, [ + generator.createElement('div', 'adlp-filter-header', '', null, [ + generator.createElement('span', '', 'payments.filter.filter'), + generator.createElement('button', 'adlp-close-button', '', { onClick: closeFilter }) + ]), + dataContainer, + generator.createElement('span', 'adlp-buttons', '', null, [clearButton, applyButton]) + ]) + ]); + + const element = generator.createElement('div', 'adl-multiselect-filter', '', null, [button, filterContainer]); + + window.addEventListener('click', (event) => { + if (!element.contains(event.target) && event.target !== element) { + closeFilter(); + } + }); + + return element; + }; + + AdyenFE.components.TableFilter = { + create + }; +})(); diff --git a/Resources/views/backend/_resources/js/TemplateService.js b/Resources/views/backend/_resources/js/TemplateService.js new file mode 100644 index 00000000..e6441f6e --- /dev/null +++ b/Resources/views/backend/_resources/js/TemplateService.js @@ -0,0 +1,77 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + function TemplateService() { + /** + * The configuration object for all templates. + */ + let templates = {}; + let mainPlaceholder = '#adl-main-page-holder'; + + /** + * Gets the main page DOM element. + * + * @returns {HTMLElement} + */ + this.getMainPage = () => document.querySelector(mainPlaceholder); + + /** + * Gets the main header element. + * + * @returns {HTMLElement} + */ + this.getHeaderSection = () => document.getElementById('adl-header-section'); + + /** + * Clears the main page. + * + * @return {HTMLElement} + */ + this.clearMainPage = () => { + this.clearComponent(this.getMainPage()); + }; + + /** + * Sets the content templates. + * + * @param {{}} configuration + */ + this.setTemplates = (configuration) => { + templates = configuration; + }; + + /** + * Gets the template with translated text. + * + * @param {string} templateId + * + * @return {string} HTML as string. + */ + this.getTemplate = (templateId) => translate(templates[templateId]); + + /** + * Removes component's children. + * + * @param {Element} component + */ + this.clearComponent = (component) => { + while (component.firstChild) { + component.removeChild(component.firstChild); + } + }; + + /** + * Replaces all translation keys in the provided HTML. + * + * @param {string} html + * @return {string} + */ + const translate = (html) => { + return html ? AdyenFE.translationService.translateHtml(html) : ''; + }; + } + + AdyenFE.templateService = new TemplateService(); +})(); diff --git a/Resources/views/backend/_resources/js/TranslationService.js b/Resources/views/backend/_resources/js/TranslationService.js new file mode 100644 index 00000000..7cbd992f --- /dev/null +++ b/Resources/views/backend/_resources/js/TranslationService.js @@ -0,0 +1,125 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * A Translation service. This class turns an input key and params to the translated text. + * The translations are used from the global AdyenFE.translations object. It expects two keys in this object: + * 'current' and 'default', where 'current' holds the translations for the current language, + * and 'default' holds the translations in the default language. The 'default' will be used as a fallback if + * the 'current' object does not have the given entry. Both properties should be objects with the "section - key" + * format. For example: + * current: { + * login: { + * title: 'The title', + * subtitle: 'This is the subtitle of the %s app.' + * }, + * secondPage: { + * title: 'The second page title', + * description: 'Use this page to set the second thing.' + * } + * } + * + * With this in mind, the translation keys are in format "section.key", for example "login.title". + * + * @constructor + */ + function TranslationService() { + /** + * Gets the translation from the dictionary if exists. + * + * @param {'default' | 'current'} type + * @param {string} group + * @param {string | string[]} key + * @returns {null|string} + */ + const getTranslation = (type, group, key) => { + if (AdyenFE.translations[type][group] && AdyenFE.translations[type][group]) { + let value = AdyenFE.translations[type][group]; + if (Array.isArray(key)) { + return key.reduce((value, key) => { + if (value && value.hasOwnProperty(key)) { + return value[key]; + } + + return null; + }, value); + } + + if (value && value.hasOwnProperty(key)) { + return value[key]; + } + + return null; + } + + return null; + }; + + /** + * Replaces the parameters in the given text, if any. + * + * @param {string} text + * @param {[]} params + * @return {string} + */ + const replaceParams = (text, params) => { + if (!params) { + return text; + } + + let i = 0; + return text.replace(/%s/g, function () { + const param = params[i] !== undefined ? params[i] : '%s'; + i++; + + return param; + }); + }; + + /** + * Returns a translated string based on the input key and given parameters. If the string to translate + * has parameters, the placeholder is "%s". For example: Input key %s is not valid. This method will + * replace parameters in the order given in the params array, if any. + * + * @param {string} key The translation key in format "group.key". + * @param {[]} [params] An array of parameters to be replaced in the output string. + * + * @return {string} + */ + this.translate = (key, params) => { + const [group, ...keys] = key.split('.'); + + const result = getTranslation('current', group, keys) || getTranslation('default', group, keys); + if (result !== null) { + return replaceParams(result, params); + } + + return replaceParams(key, params); + }; + + /** + * Replaces the translations in the given HTML code. + * + * @param {string} html + * @return {string} The updated HTML. + */ + this.translateHtml = (html) => { + // Replace the placeholders for translations. They are in the format {$key|param1|param2}. + let format = /{\$[.\-_A-Za-z|]+}/g; + const me = this; + + return html.replace(format, (key) => { + // remove the placeholder characters to get "key|param1|param2" + key = key.substring(2, key.length - 1); + // split parameters + let params = key.split('|'); + + return me.translate(params[0], params.slice(1)) || key; + }); + }; + } + + AdyenFE.translationService = new TranslationService(); +})(); diff --git a/Resources/views/backend/_resources/js/UtilityService.js b/Resources/views/backend/_resources/js/UtilityService.js new file mode 100644 index 00000000..74b3e14b --- /dev/null +++ b/Resources/views/backend/_resources/js/UtilityService.js @@ -0,0 +1,168 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + function UtilityService() { + let loaderCount = 0; + + /** + * Shows the HTML node. + * + * @param {HTMLElement} element + */ + this.showElement = (element) => { + element?.classList.remove('adls--hidden'); + }; + + /** + * Hides the HTML node. + * + * @param {HTMLElement} element + */ + this.hideElement = (element) => { + element?.classList.add('adls--hidden'); + }; + + /** + * Enables loading spinner. + */ + this.showLoader = () => { + if (loaderCount === 0) { + this.showElement(document.getElementById('adl-spinner')); + } + + loaderCount++; + }; + + /** + * Hides loading spinner. + */ + this.hideLoader = () => { + loaderCount--; + if (loaderCount === 0) { + this.hideElement(document.getElementById('adl-spinner')); + } + }; + + /** + * Shows flash message. + * + * @note Only one flash message will be shown at the same time. + * + * @param {string} message + * @param {'error' | 'warning' | 'success'} status + * @param {number?} clearAfter Time in ms to remove alert message. + */ + this.createFlashMessage = (message, status, clearAfter) => { + return AdyenFE.elementGenerator.createFlashMessage(message, status, clearAfter); + }; + + /** + * Creates the 401 error flash message. + * + * @param {string} message + */ + this.create401FlashMessage = (message) => { + this.remove401Message(); + const messageElement = AdyenFE.elementGenerator.createFlashMessage(message, 'error'); + messageElement.classList.add('adlv--401-error'); + AdyenFE.templateService.getHeaderSection().append(messageElement); + }; + + /** + * Removes the 401 flash message. + */ + this.remove401Message = () => { + AdyenFE.templateService + .getHeaderSection() + .querySelectorAll(`.adlv--401-error`) + .forEach((e) => e.remove()); + }; + + /** + * Creates a toaster message. + * + * @param {string} message A message translation key. + */ + this.createToasterMessage = (message) => { + document.getElementById('adl-page').append(AdyenFE.elementGenerator.createToaster(message)); + }; + + /** + * Updates a form's footer state based on the number of changes. + * + * @param {number} numberOfChanges + * @param {boolean} disableCancel + */ + this.renderFooterState = (numberOfChanges, disableCancel = true) => { + if (numberOfChanges) { + document.querySelector('.adl-form-footer .adlp-changes-count')?.classList.add('adls--active'); + document.querySelector('.adl-form-footer .adlp-actions .adlp-save').disabled = false; + document.querySelector('.adl-form-footer .adlp-actions .adlp-cancel').disabled = false; + } else { + document.querySelector('.adl-form-footer .adlp-changes-count')?.classList.remove('adls--active'); + document.querySelector('.adl-form-footer .adlp-actions .adlp-save').disabled = true; + document.querySelector('.adl-form-footer .adlp-actions .adlp-cancel').disabled = disableCancel; + } + }; + + /** + * Creates deep clone of an object with object's properties. + * Removes object's methods. + * + * @note Object cannot have values that cannot be converted to json (undefined, infinity etc). + * + * @param {object} obj + * @return {object} + */ + this.cloneObject = (obj) => JSON.parse(JSON.stringify(obj || {})); + + /** + * Gets the first ancestor element with the corresponding class name. + * + * @param {HTMLElement} element + * @param {string} className + * @return {HTMLElement} + */ + this.getAncestor = (element, className) => { + let parent = element?.parentElement; + + while (parent) { + if (parent.classList.contains(className)) { + break; + } + + parent = parent.parentElement; + } + + return parent; + }; + + /** + * Check if two arrays are equal. + * + * @param {any[]} source + * @param {any[]} target + * @return {boolean} TRUE if arrays are equal; otherwise, FALSE. + */ + this.compareArrays = (source, target) => { + if (source.length !== target.length) { + return false; + } + + const sortedSource = source.slice().sort(); + const sortedTarget = target.slice().sort(); + + for (let i = 0; i < sortedSource.length; i++) { + if (sortedSource[i] !== sortedTarget[i]) { + return false; + } + } + + return true; + }; + } + + AdyenFE.utilities = new UtilityService(); +})(); diff --git a/Resources/views/backend/_resources/js/ValidationService.js b/Resources/views/backend/_resources/js/ValidationService.js new file mode 100644 index 00000000..4c4bcc6c --- /dev/null +++ b/Resources/views/backend/_resources/js/ValidationService.js @@ -0,0 +1,267 @@ +if (!window.AdyenFE) { + window.AdyenFE = {}; +} + +(function () { + /** + * @typedef ValidationMessage + * @property {string} code The message code. + * @property {string} field The field name that the error is related to. + * @property {string} message The error message. + */ + + const validationRule = { + numeric: 'numeric', + integer: 'integer', + required: 'required', + greaterThanZero: 'greaterThanZero', + minValue: 'minValue', + maxValue: 'maxValue', + nonNegative: 'nonNegative', + greaterThanX: 'greaterThanX' + }; + + const { templateService, utilities, translationService } = AdyenFE; + + /** + * Validates if the input has a value. If the value is not set, adds an error class to the input element. + * + * @param {HTMLInputElement|HTMLSelectElement} input + * @param {string?} message + * @return {boolean} + */ + const validateRequiredField = (input, message) => { + return validateField(input, !input.value?.trim() || (input.type === 'checkbox' && !input.checked), message); + }; + + /** + * Validates a numeric input. + * + * @param {HTMLInputElement} input + * @param {string?} message + * @return {boolean} Indication of the validity. + */ + const validateNumber = (input, message) => { + const ruleset = input.dataset?.validationRule ? input.dataset.validationRule.split(',') : []; + let result = true; + + if (!validateField(input, Number.isNaN(input.value), message)) { + return false; + } + + const value = Number(input.value); + ruleset.forEach((rule) => { + if (!result) { + // break on first false rule + return; + } + + let condition = false; + let subValue = null; + if (rule.includes('|')) { + [rule, subValue] = rule.split('|'); + } + + // condition should be positive for valid values + switch (rule) { + case validationRule.integer: + condition = Number.isInteger(value); + break; + case validationRule.greaterThanZero: + condition = value > 0; + break; + case validationRule.minValue: + condition = value >= Number(subValue); + break; + case validationRule.maxValue: + condition = value <= Number(subValue); + break; + case validationRule.nonNegative: + condition = value >= 0; + break; + case validationRule.required: + condition = !!input.value?.trim(); + break; + case validationRule.greaterThanX: + condition = value >= Number(document.querySelector(`input[name="${subValue}"]`)?.value); + break; + default: + return; + } + + if (!validateField(input, !condition, message)) { + result = false; + } + }); + + return result; + }; + + /** + * Validates a list of numbers. + * + * @param {HTMLInputElement} input + * @param {boolean} [required=true] + * @param {boolean} [decimal=true] + * @returns {boolean} + */ + const validateNumberList = (input, required = true, decimal = true) => { + let error; + const value = input.value; + if (!value.trim()) { + if (!required) { + return true; + } + + error = 'validation.requiredField'; + } else { + const values = value.split(',').map((value) => value.trim()); + if (values.map((value) => Number.isNaN(Number(value)) || Number(value) <= 0).includes(true)) { + error = decimal ? 'validation.invalidNumberInList' : 'validation.invalidWholeNumberInList'; + } else if ( + values.filter((value, index) => { + return values.indexOf(value) !== index; + }).length > 0 + ) { + error = 'validation.duplicateNumberInList'; + } else if (!decimal) { + values.forEach((value) => { + if (!Number.isInteger(Number(value))) { + error = 'validation.invalidWholeNumberInList'; + } + }); + } + } + + return validateField(input, !!error, error); + }; + + /** + * Validates if the input is a valid email. If not, adds the error class to the input element. + * + * @param {HTMLInputElement} input + * @param {string?} message + * @return {boolean} + */ + const validateEmail = (input, message) => { + let regex = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + return validateField(input, !regex.test(String(input.value).toLowerCase()), message); + }; + + /** + * Validates if the input is a valid URL. If not, adds an error class to the input element. + * + * @param {HTMLInputElement} input + * @param {string?} message + * @return {boolean} + */ + const validateUrl = (input, message) => { + let regex = /(https?:\/\/)([\w\-])+\.([a-zA-Z]{2,63})([\/\w-]*)*\/?\??([^#\n\r]*)?#?([^\n\r]*)/m; + + return validateField(input, !regex.test(String(input.value).toLowerCase()), message); + }; + + /** + * Validates if the input field is longer than a specified number of characters. + * If so, adds an error class to the input element. + * + * @param {HTMLInputElement} input + * @param {string?} message + * @return {boolean} + */ + const validateMaxLength = (input, message) => { + return validateField(input, input.dataset.maxLength && input.value.length > input.dataset.maxLength, message); + }; + + /** + * Handles validation errors. These errors come from the back end. + * + * @param {ValidationMessage[]} errors + */ + const handleValidationErrors = (errors) => { + for (const error of errors) { + markFieldGroupInvalid(`[name=${error.field}]`, error.message); + } + }; + + /** + * Marks a field as invalid. + * + * @param {string} fieldSelector The field selector. + * @param {string} message The message to display. + * @param {Element} [parent] A parent element. + */ + const markFieldGroupInvalid = (fieldSelector, message, parent) => { + if (!parent) { + parent = templateService.getMainPage(); + } + + const inputEl = parent.querySelector(fieldSelector); + inputEl && setError(inputEl, message); + }; + + /** + * Sets error for an input. + * + * @param {HTMLElement} element + * @param {string?} message + */ + const setError = (element, message) => { + const parent = utilities.getAncestor(element, 'adl-field-wrapper'); + parent && parent.classList.add('adls--error'); + if (message) { + let errorField = parent.querySelector('.adlp-input-error'); + if (!errorField) { + errorField = AdyenFE.elementGenerator.createElement('span', 'adlp-input-error', message); + parent.append(errorField); + } + + errorField.innerHTML = translationService.translate(message); + } + }; + + /** + * Removes error from input form group element. + * + * @param {HTMLElement} element + */ + const removeError = (element) => { + const parent = utilities.getAncestor(element, 'adl-field-wrapper'); + parent && parent.classList.remove('adls--error'); + }; + + /** + * Validates the condition against the input field and marks field invalid if the error condition is met. + * + * @param {HTMLElement} element + * @param {boolean} errorCondition Error condition. + * @param {string?} message + * @return {boolean} + */ + const validateField = (element, errorCondition, message) => { + if (errorCondition) { + setError(element, message); + + return false; + } + + removeError(element); + + return true; + }; + + AdyenFE.validationService = { + setError, + removeError, + validateEmail, + validateNumber, + validateNumberList, + validateUrl, + validateMaxLength, + validateField, + validateRequiredField, + handleValidationErrors + }; +})(); diff --git a/Resources/views/backend/_resources/lang/de.json b/Resources/views/backend/_resources/lang/de.json new file mode 100644 index 00000000..53c3ece8 --- /dev/null +++ b/Resources/views/backend/_resources/lang/de.json @@ -0,0 +1,60 @@ +{ + "general": { + "basicSettings": "Einstellungen", + "systemInfo": "Systeminformationen", + "help": "Hilfe", + "loading": "Lädt...", + "save": "Speichern", + "saveChanges": "Speichern", + "accept": "Akzeptieren", + "cancel": "Abbrechen", + "continue": "Weiter", + "add": "Hinzufügen", + "edit": "Bearbeiten", + "delete": "Löschen", + "discard": "Verwerfen" + }, + "validation": { + "requiredField": "Dies ist ein Pflichtfeld.", + "invalidField": "This field is invalid.", + "invalidEmail": "Das Feld muss eine gültige E-Mail-Adresse sein.", + "maxLength": "Dieses Feld darf maximal %s Zeichen enthalten.", + "numeric": "Bitte geben Sie einen numerischen Wert ein.", + "greaterThanZero": "Der Wert muss größer als 0 sein.", + "nonNegative": "Das Feld darf keine negativen Werte enthalten.", + "numberOfDecimalPlaces": "Das Feld muss 2 Dezimalstellen haben.", + "integer": "Das Feld muss eine ganze Zahl sein.", + "invalidUrl": "Dieses Feld muss eine gültige URL enthalten.", + "invalidFieldValue": "Dieses Feld muss auf „%s“ gesetzt werden.", + "invalidMaxValue": "Der maximale Wert beträgt %s." + }, + "login": { + "apiKey": "API-Schlüssel", + "apiKeyIncorrect": "Falscher API-Schlüssel", + "validateApiKey": "API-Schlüssel bestätigen" + }, + "countries": { + "ES": "Spanien", + "DE": "Deutschland", + "FR": "Frankreich", + "IT": "Italien", + "AT": "Österreich", + "NL": "Niederlande", + "BE": "Belgien", + "PT": "Portugal", + "TR": "Türkei", + "IE": "Irland", + "GB": "Vereinigtes Königreich", + "HU": "Ungarn", + "PL": "Polen", + "CH": "Schweiz", + "LU": "Luxemburg", + "AR": "Argentinien", + "US": "Vereinigte Staaten", + "BO": "Bolivien", + "MX": "Mexiko", + "CL": "Chile", + "CZ": "Tschechien", + "SE": "Schweden" + } +} \ No newline at end of file diff --git a/Resources/views/backend/_resources/lang/en.json b/Resources/views/backend/_resources/lang/en.json new file mode 100644 index 00000000..0a521076 --- /dev/null +++ b/Resources/views/backend/_resources/lang/en.json @@ -0,0 +1,704 @@ +{ + "general": { + "save": "Save", + "saveChanges": "Save changes", + "unsavedChanges": "Unsaved changes", + "discardChanges": "Discard changes", + "cancel": "Cancel", + "yes": "Yes", + "no": "No", + "ok": "Ok", + "back": "Back", + "confirm": "Confirm", + "enable": "Enable", + "disable": "Disable", + "add": "Add", + "edit": "Edit", + "delete": "Remove", + "discard": "Discard", + "any": "ANY", + "clear": "Clear", + "apply": "Apply", + "search": "Search...", + "viewDetails": "View details", + "changesSaved": "Changes saved successfully.", + "errors": { + "InvalidOperationException": "An error occurred on the server side. Please check the data and try again.", + "unknown": "An unknown error occurred. Please try again later.", + "saveEntityFailed": "Failed to save entity because %s", + "getEntityFailed": "Failed to retrieve entity because %s", + "failedToRetrieveStores": "Failed to retrieve stores.", + "failedToRetrieveOrderStatuses": "Failed to retrieve order statuses.", + "general": { + "unhandled": "An error occurred. Please check all inputs and try again." + }, + "stores": { + "invalidIdAndStatus": "Status Id and status name are invalid." + }, + "connection": { + "emptyStoreId": "Empty store ID.", + "invalidMode": "Invalid environment.", + "emptyData": "API key data must not be empty.", + "settingsNotFound": "Connection settings not found.", + "invalidRoles": "User does not have all necessary roles.", + "invalidSettings": "Connection settings are not valid.", + "credentialsDoNotExist": "Api credential details do not exist.", + "testKeyChanged": "Test api key has been changed.", + "liveKeyChanged": "Live api key has been changed.", + "testMerchantIdChanged": "Test merchant ID has been changed.", + "liveMerchantIdChanged": "Live merchant ID has been changed.", + "companyLevelKey": "We didn't recognize this API key on merchant level. Check that you have copied the API key on merchant level from your Adyen Customer Area.", + "invalidKey": "We didn't recognize this API key. Check that you have copied the API key for the right environment from your Adyen Customer Area.", + "invalidMerchant": "Merchant account is not connected to provided credentials.", + "originFailed": "Adding allowed origin failed.", + "clientKeyGenerationFailed": "Failed to generate client key." + }, + "payments": { + "requiredFieldsError": "Method id and code fields are required.", + "notConfigured": "Payment method with id %s is not configured", + "emptyDataError": "Empty payment method data.", + "failedToRetrieveMethods": "Failed to retrieve payment methods.", + "saveFailed": "Failed to save payment method.", + "updateFailed": "Failed to update payment method.", + "deleteFailed": "Failed to remove payment method.", + "failedToRetrieveMethodsAdyen": "Failed to retrieve payment methods from Adyen.", + "invalidCardConfig": "The configuration of installments is invalid." + }, + "generalSettings": { + "captureDelayError": "Number of days must be between 1 and 7.", + "retentionPeriodError": "Minimum number of retention period is 60." + }, + "webhooks": { + "merchantError": "Error occured: Merchant with ID %s is not found.", + "failedToRegisterWebhook": "Error occured: Unable to register webhooks on Adyen API. Make sure that storefront is publicly available, so Adyen webhooks can reach the store.", + "hmacError": "Error occured: Generating HMAC on Adyen API failed." + } + }, + "selectedItems": "%s items selected" + }, + "sidebar": { + "newVersionAvailable": "New version is available", + "gettingStarted": "Getting started", + "setupConnection": "Setup", + "selectMerchantAccount": "Merchant account", + "paymentMethods": "Payment methods", + "configurePaymentMethods": "Payment methods", + "settings": "Settings", + "generalSettings": "General settings", + "orderStatusMapping": "Order status mapping", + "adyenGiving": "Adyen Giving", + "notifications": "Notifications", + "webhookNotification": "Webhook notifications", + "shopNotification": "Shop event notifications", + "quickLinks": "Quick links", + "goToDocumentation": "Go to Adyen docs", + "goToCustomerArea": "Go to Customer Area", + "goToAdyenGiving": "Go to Adyen Giving", + "troubleshooting": "System troubleshooting", + "contactAdyenSupport": "Contact Adyen support", + "systemInformation": "System information" + }, + "mainHeader": { + "merchant": "Merchant", + "switchStore": "Switch Store", + "mode": "Environment", + "download": "Download new version", + "version": "Version %s" + }, + "maintenance": { + "title": "Store maintenance mode has been detected", + "description": "Integration won't work while the store is in maintenance mode. Make sure that storefront is publicly available, so Adyen webhooks can reach the store." + }, + "validation": { + "requiredField": "This field is required.", + "invalidField": "This field is invalid.", + "invalidEmail": "This field must be a valid email.", + "maxLength": "The field cannot have more than %s characters.", + "numeric": "Value must be a valid number with max two decimals.", + "greaterThanZero": "Value must be greater than 0.", + "nonNegative": "Field cannot have a negative value.", + "numberOfDecimalPlaces": "Field must have 2 decimal places.", + "integer": "Field must be an integer.", + "invalidNumberInList": "Each value must be a positive number.", + "invalidWholeNumberInList": "Each value must be a positive whole number.", + "duplicateNumberInList": "The list cannot contain duplicate values.", + "invalidUrl": "Field must be a valid URL.", + "invalidFieldValue": "Field must be set to \"%s\".", + "invalidMaxValue": "Maximal allowed value is %s.", + "invalidImageType": "Provide a file in one of the supported image types (PNG, JPG, SVG).", + "invalidImageSize": "The provided file exceeds the limit of 10MB." + }, + "connection": { + "title": "Setup", + "title_merchant": "Merchant account", + "subtitle": "Make sure you're logged in to the Adyen Customer Area. You will need top copy several settings from there to here. If you don't have an account yet,
sign up.", + "subtitle_merchant": "", + "environment": { + "title": "Environment", + "description": "Connect to the test environment to test your store's payments. When you're ready to process for real shoppers, connect to live.", + "options": { + "test": "Test", + "live": "Live" + } + }, + "apiKey": { + "title": "Merchant API key", + "description": "Copy the API key for the merchant account you want to connect. You can find this in the Adyen Customer Area.", + "error": "API key is required." + }, + "merchant": { + "title": "Merchant account", + "description": "Select which Adyen merchant account you want to connect to this store.", + "placeholder": "Select Adyen merchant account", + "error": "Merchant account is required." + }, + "next": "Next", + "connect": "Connect", + "disconnect": "Disconnect", + "disconnectModal": { + "title": "Disconnect from Adyen?", + "message": "Are you sure you want to disconnect your shop from Adyen? This will remove all configured payment methods and settings, and shoppers will not be able to complete purchases on your shop." + }, + "changeEnvironmentModal": { + "title": "Change the environment?", + "message": "Are you sure you want to change the environment? This will remove all configured payment methods and settings on the current environment, and reconnect you to the newly selected environment." + }, + "validateCredentials": "Validate credentials", + "errors": { + "invalidApiKey": "We didn't recognize this API key. Check that you have copied the API key for the right environment from your Adyen Customer Area.", + "credentialsValidationError": "Connection settings are not valid.", + "webhookValidationError": "Webhook validation failed. Make sure that storefront is publicly available, so Adyen webhooks can reach the store." + }, + "messages": { + "validCredentials": "Your connection to Adyen was successful.", + "connectionUpdated": "Your connection details are updated and connection to Adyen was successful." + } + }, + "payments": { + "list": { + "filter": "Filter", + "paymentMethod": "Payment method", + "currencies": "Currencies", + "regions": "Supported regions", + "type": "Type", + "status": "Status", + "statusActive": "Active", + "statusInactive": "Inactive", + "statusHint": "Payment method status in Adyen Customer Area.", + "actions": "Actions", + "actionsAdd": "Add", + "multiItemLabel": "%s and %s others", + "noMethodsForFilter": "We couldn't find any payment methods matching the chosen criteria." + }, + "filter": { + "filter": "Filter", + "types": { + "label": "Type", + "labelPlural": "Types", + "selectPlaceholder": "Select types to filter by" + }, + "statuses": { + "label": "Status", + "labelPlural": "Statuses", + "selectPlaceholder": "Select a status to filter by" + }, + "currencies": { + "label": "Currency", + "labelPlural": "Currencies", + "selectPlaceholder": "Select currencies to filter by" + }, + "countries": { + "label": "Country", + "labelPlural": "Countries", + "selectPlaceholder": "Select countries to filter by" + }, + "resetAll": "Reset all" + }, + "active": { + "title": "Payment methods", + "description": "This overview shows payment methods that are available for shoppers at this store's checkout.", + "addMethod": "Add payment method", + "noMethodsMessage": "There are no Adyen payment methods configured to this store." + }, + "add": { + "title": "Add payment method", + "description": "Choose a new payment method to be available in your store.", + "noMethodsMessage": "There are no Adyen payment methods.", + "back": "Back" + }, + "configure": { + "title": "Setup %s payment method", + "description": "Configure your preferences for this payment method. When you save your preferences, the payment method will be available on your store. Learn more", + "methodAdded": "Added new payment method.", + "methodSaved": "Payment method is saved.", + "fields": { + "name": { + "label": "Payment method name", + "description": "This is the name the shoppers will see on the checkout page." + }, + "description": { + "label": "Payment method description (optional)", + "description": "This is the description the shoppers will see on the checkout page. You can show shoppers more information about this payment method." + }, + "surchargeType": { + "label": "Surcharge type", + "description": "You can decide whether to add a surcharge when a shopper uses this payment method.", + "placeholder": "Select surcharge type", + "none": "None", + "fixed": "Fixed surcharge", + "percent": "Percent surcharge", + "combined": "Combined surcharge" + }, + "fixedSurcharge": { + "label": "Fixed surcharge", + "description": "Enter the specific amount to add to the order total when a shopper uses this payment method. Set the amount in default store currency." + }, + "percentSurcharge": { + "label": "Percent surcharge", + "description": "Enter the percentage amount of the order total that is added to the total when a shopper uses this payment method." + }, + "surchargeLimit": { + "label": "Surcharge limit", + "description": "Enter the threshold for the total surcharge. This is the maximum surcharge that a shopper can be charged when using this payment method. Set the amount in default store currency.", + "error": "Surcharge limit must be a positive number with maximum two decimals and greater or equal to the fixed surcharge." + }, + "logo": { + "label": "Upload payment method logo", + "description": "", + "error": "Provide a file in one of the supported image types (PNG, JPG, SVG).", + "errorSize": "The selected file exceeds the limit of 10MB.", + "placeholder": "Drop image or click here to upload" + }, + "creditCardFields": { + "showLogos": { + "label": "Show credit card logos", + "description": "You can decide whether to show shoppers credit card logos on the checkout." + }, + "singleClickPayment": { + "label": "Enable single click payments", + "description": "Do you want shoppers to have the option to save their card details for future payments?" + }, + "sendBasket": { + "label": "Enable L2/L3 data", + "description": "Do you want to send additional data to shopper's card statements? This is useful for companies to track corporate card usage when a single card can have multiple users. Learn more" + } + }, + "oneyFields": { + "supportedInstallments": { + "label": "Supported installments", + "description": "Do you want to allow shoppers to pay their bill in installments? Learn more", + "placeholder": "Select one or more" + } + }, + "installmentFields": { + "installments": { + "label": "Enable installments", + "description": "Do you want to allow shoppers to pay their bill in installments? Learn more" + }, + "installmentAmounts": { + "label": "Show installment amounts", + "description": "You can decide whether to show shoppers installment amounts on the checkout." + }, + "installmentCountries": { + "label": "Allowed installment countries", + "description": "You can decide for which countries installments should be offered to shoppers on the checkout." + }, + "minimumAmount": { + "label": "Minimum installment amount (optional)", + "description": "You can specify minimum order amount threshold for which installments should be offered to shoppers on the checkout." + }, + "numberOfInstallments": { + "label": "Number of installments", + "description": "Provide a comma-separated list of options representing the number of installments offered to shoppers on checkout. Learn more" + } + }, + "issuersFields": { + "showLogos": { + "label": "Show issuer logos", + "description": "You can decide whether to show shoppers bank issuer logos on the checkout." + }, + "bankIssuer": { + "label": "Bank issuer description (optional)", + "description": "This is a bank issuer dropdown description. The default description is 'Select your bank'" + } + }, + "applePayFields": { + "merchantId": { + "label": "Apple Pay merchant ID", + "description": "Enter identifier of the Apple Pay merchant." + }, + "merchantName": { + "label": "Apple Pay merchant name", + "description": "Enter name of the Apple Pay merchant." + }, + "displayButtonOn": { + "label": "Enable express checkout", + "description": "Do you want to show Apple Pay Express Checkout on the product, cart, and checkout pages?" + } + }, + "amazonPayFields": { + "publicKeyId": { + "label": "Amazon Pay public key ID", + "description": "Enter identifier of the Amazon Pay public key." + }, + "merchantId": { + "label": "Amazon Pay merchant ID", + "description": "Enter identifier of the Amazon Pay merchant." + }, + "storeId": { + "label": "Store ID", + "description": "Enter identifier of the store" + }, + "displayButtonOn": { + "label": "Enable express checkout", + "description": "Do you want to show Amazon Pay Express Checkout on the product, cart, and checkout pages?" + } + }, + "googlePayFields": { + "gatewayMerchantId": { + "label": "Google Pay merchant gateway ID", + "description": "Enter identifier of the Google Pay merchant gateway." + }, + "merchantId": { + "label": "Google Pay merchant ID", + "description": "Enter identifier of the Google Pay merchant." + }, + "displayButtonOn": { + "label": "Enable express checkout", + "description": "Do you want to show Google Pay Express Checkout on the product, cart, and checkout pages?" + } + }, + "paypalFields": { + "displayButtonOn": { + "label": "Enable express checkout", + "description": "Do you want to show PayPal Express Checkout on the product, cart, and checkout pages?" + } + } + } + }, + "delete": { + "title": "Remove payment method", + "description": "Do you want to remove this payment method from your store? Shoppers will no longer be able to pay with this payment method." + }, + "switchToStore": { + "title": "Unsaved changes", + "description": "Changes you made are not saved. Are you sure that you want to proceed with switching to another store?" + }, + "messages": { + "methodAdded": "Added new payment method." + }, + "paymentTypes": { + "creditOrDebitCard": "Credit and Debit cards", + "buyNowPayLater": "Buy now - pay later", + "cashOrAtm": "Cash and ATM payment methods", + "directDebit": "Direct Debit", + "onlinePayments": "Online payments", + "wallet": "Wallets", + "prepaidAndGiftCard": "Prepaid and gift cards", + "mobile": "Mobile" + } + }, + "settings": { + "general": { + "title": "General settings", + "description": "These settings are applicable globally and will be implemented across all payment methods.", + "fields": { + "basketItemSync": { + "label": "Send basket data", + "description": "Sending basket data as additional risk fields makes it easier to detect fraud and can be used for custom risk rules. Do you want to send this data to Adyen?" + }, + "capture": { + "label": "Capture", + "description": "Some payment methods support separate authorisation and capture. By default, capture is set to immediately after authorisation, but you can choose to delay the capture for these payment methods. This can be helpful if you want to have the option to cancel a payment in case of issues with shipment, for example.", + "placeholder": "Select capture type", + "error": "Capture type is mandatory", + "delayed": "Delayed", + "immediate": "Immediate", + "manual": "Manual" + }, + "captureDelay": { + "label": "Capture delay", + "description": "How many days after authorisation do you want the payment to be captured? Enter a number from 1 to 7.", + "error": "Number of days must be between 1 and 7." + }, + "shipmentStatus": { + "label": "Capture on status change", + "description": "Select the status that will trigger the payment capture.", + "placeholder": "Status" + }, + "retentionPeriod": { + "label": "Notifications retention period", + "description": "Define number of days that notification logs will be stored in the system.", + "error": "Minimum number of the retention period is 60 days." + } + } + }, + "orderStatusMapping": { + "title": "Order status mapping", + "description": "This page allows you to map shop order statuses to Adyen payment statuses.", + "none": "None", + "fields": { + "inProgress": { + "label": "In progress", + "description": "The Adyen payment status In progress should represent the following shop status:", + "placeholder": "Select status" + }, + "pending": { + "label": "Pending", + "description": "The Adyen payment status Pending should represent the following shop status:", + "placeholder": "Select status" + }, + "paid": { + "label": "Paid", + "description": "The Adyen payment status Paid should represent the following shop status:", + "placeholder": "Select status" + }, + "failed": { + "label": "Failed", + "description": "The Adyen payment status Failed should represent the following shop status:", + "placeholder": "Select status" + }, + "refunded": { + "label": "Refunded", + "description": "The Adyen payment status Refunded should represent the following shop status:", + "placeholder": "Select status" + }, + "cancelled": { + "label": "Cancelled", + "description": "The Adyen payment status Cancelled should represent the following shop status:", + "placeholder": "Select status" + }, + "partiallyRefunded": { + "label": "Partially refunded", + "description": "The Adyen payment status Partially refunded should represent the following shop status:", + "placeholder": "Select status" + }, + "new": { + "label": "New", + "description": "The Adyen payment status New should represent the following shop status:", + "placeholder": "Select status" + }, + "chargeBack": { + "label": "Charge back", + "description": "The Adyen payment status Charge back should represent the following shop status:", + "placeholder": "Select status" + } + } + }, + "adyenGiving": { + "title": "Adyen giving", + "description": "With Adyen Giving, your shoppers can have the option to donate to a charity of your choice during checkout. Make sure you have an Adyen merchant account set up for Adyen Giving, or contact your Adyen Account Manager or Support. Learn more", + "fields": { + "enableAdyenGiving": { + "label": "Enable Adyen Giving", + "description": "You need a charity account to enable Adyen Giving. Do you want to enable Adyen Giving?" + }, + "charityName": { + "label": "Charity name", + "description": "Enter the name of the charity. This will be shown to shoppers on the donation page." + }, + "charityDescription": { + "label": "Charity description", + "description": "Enter a short description of the charity to show to shoppers on the donation page." + }, + "charityMerchantAccount": { + "label": "Charity merchant account", + "description": "Enter your Adyen merchant account name for charity." + }, + "donationAmount": { + "label": "Donation Amounts", + "description": "Enter suggested donation amounts for shoppers to choose from. Use comma-separated values in your default store currency. Example: 2, 3, 5, 10" + }, + "charityWebsite": { + "label": "Charity website", + "description": "Enter the link to the charity website." + }, + "logo": { + "label": "Logo in Adyen Giving component", + "description": "", + "placeholder": "Drop image or click here to upload" + }, + "backgroundImage": { + "label": "Background image for Adyen Giving component", + "description": "", + "placeholder": "Drop image or click here to upload" + } + } + }, + "system": { + "title": "System information and troubleshooting", + "description": "This page comprehensively details the integration's current configuration, system status, and diagnostic information. ", + "fields": { + "debugMode": { + "label": "Debug mode", + "description": "Enable debug log level for Adyen payment requests." + }, + "adyenWebhooksValidation": { + "label": "Adyen webhooks validation", + "description": "Validate whether webhooks are received from Adyen to your store." + }, + "integrationConfigurationValidation": { + "label": "Integration configuration validation", + "description": "Performs validation if background tasks are executed in your store." + }, + "downloadSystemInformation": { + "label": "Download system information", + "description": "Download the system information report so that you can pass it to Adyen support for easier and faster troubleshooting of the issue." + }, + "contactAdyenSupport": { + "label": "Contact Adyen support", + "description": "If you need help troubleshooting the issue, please feel free to contact our support." + } + }, + "buttons": { + "validate": "Validate", + "downloadReport": "Download report", + "contactAdyenSupport": "Contact Adyen support" + }, + "messages": { + "successWebhookValidation": "Webhook validated successfully", + "failedWebhookValidation": "Store was not able to receive webhook from Adyen", + "downloadReportText": "Download the full report here.", + "successIntegrationValidation": "Auto-test completed successfully", + "failedIntegrationValidation": "Auto-test did not complete successfully" + }, + "debugModeEnabled": "Debug mode enabled.", + "debugModeDisabled": "Debug mode disabled." + } + }, + "notifications": { + "shop": { + "title": "Shop event notifications", + "description": "This page displays any important store events that were not able to be synced with Adyen.", + "noNotificationsMessage": "There are no shop notifications.", + "severity": { + "error": "Error", + "warning": "Warning", + "info": "Info" + }, + "shopEventsNotifications": { + "orderId": "Order ID", + "paymentMethod": "Payment method", + "severity": "Severity", + "dateAndTime": "Date and time", + "message": "Message", + "details": "Details" + }, + "event": { + "failedPaymentAuthorizationMessage": "Payment authorization failed.", + "failedPaymentAuthorizationDetails": "Payment authorization failed on Adyen.", + "successfulPaymentAuthorizationEventMessage": "Payment has been authorized successfully.", + "successfulPaymentAuthorizationEventDetails": "Payment has been authorized by Adyen.", + "failedCancellationEventMessage": "Cancellation failed on Adyen.", + "failedCancellationEventDetails": "Cancellation failed on Adyen.", + "failedCancellationRequestEventMessage": "Cancellation request failed.", + "failedCancellationRequestEventDetails": "Cancellation request failed.", + "successfulCancellationEventMessage": "Payment has been cancelled on Adyen.", + "successfulCancellationEventDetails": "Payment has been cancelled on Adyen.", + "successfulCancellationRequestEventMessage": "Cancellation request has been sent to Adyen.", + "successfulCancellationRequestEventDetails": "Cancellation request has been sent to Adyen.", + "failedCaptureEventMessage": "Capture failed on Adyen.", + "failedCaptureEventDetails": "Capture failed on Adyen.", + "failedCaptureRequestEventMessage": "Capture request failed.", + "failedCaptureRequestEventDetails": "Capture request failed.", + "successfulCaptureEventMessage": "Payment has been captured successfully on Adyen.", + "successfulCaptureEventDetails": "Payment has been captured successfully on Adyen.", + "successfulCaptureRequestEventMessage": "Capture request has been sent to Adyen.", + "successfulCaptureRequestEventDetails": "Capture request has been sent to Adyen.", + "failedRefundEventMessage": "Refund failed on Adyen.", + "failedRefundEventDetails": "Refund failed on Adyen.", + "failedRefundRequestEventMessage": "Refund request failed.", + "failedRefundRequestEventDetails": "Refund request failed.", + "successfulRefundEventMessage": "Payment has been refunded successfully on Adyen.", + "successfulRefundEventDetails": "Payment has been refunded successfully on Adyen.", + "successfulRefundRequestEventMessage": "Refund request has been sent to Adyen.", + "successfulRefundRequestEventDetails": "Refund request has been sent to Adyen." + } + }, + "webhook": { + "title": "Webhook notifications", + "description": "This log shows all notifications for Adyen webhooks that have been received.", + "noNotificationsMessage": "There are no webhook notifications.", + "status": { + "created": "Created", + "queued": "Queued", + "in_progress": "In progress", + "aborted": "Aborted", + "failed": "Failed", + "completed": "Completed" + }, + "webhookEventsNotifications": { + "orderId": "Order ID", + "logo": "Logo", + "paymentMethod": "Payment method", + "eventCode": "Event code", + "dateAndTime": "Date and time", + "success": "Success", + "status": "Status", + "action": "Action" + }, + "notificationDetailsModal": { + "title": "Notification details", + "reason": "Reason: ", + "failureDescription": "Failure description: ", + "paymentLink": "Go to payment in Adyen CA", + "shopLink": "Go to shop order" + } + } + }, + "countries": { + "BR": "Brazil", + "MX": "Mexico", + "TK": "Tokelau", + "TR": "Turkey", + "AU": "Australia", + "AT": "Austria", + "BE": "Belgium", + "BG": "Bulgaria", + "CA": "Canada", + "HR": "Croatia", + "CY": "Cyprus", + "CZ": "Czech Republic", + "DK": "Denmark", + "EE": "Estonia", + "FI": "Finland", + "FR": "France", + "DE": "Germany", + "GI": "Gibraltar", + "GR": "Greece", + "HK": "Hong Kong", + "HU": "Hungary", + "IS": "Iceland", + "IE": "Ireland", + "IT": "Italy", + "JP": "Japan", + "LV": "Latvia", + "LI": "Liechtenstein", + "LT": "Lithuania", + "LU": "Luxembourg", + "MT": "Malta", + "NL": "Netherlands", + "NZ": "New Zealand", + "NO": "Norway", + "PL": "Poland", + "PT": "Portugal", + "PR": "Puerto Rico", + "RO": "Romania", + "SG": "Singapore", + "SK": "Slovakia", + "SI": "Slovenia", + "ES": "Spain", + "SE": "Sweden", + "CH": "Switzerland", + "AE": "United Arab Emirates", + "GB": "United Kingdom", + "US": "United States of America" + }, + "oneyValues": { + "3x": "3x", + "4x": "4x", + "6x": "6x", + "10x": "10x", + "12x": "12x" + } +} diff --git a/Resources/views/backend/_resources/templates/sidebar.html b/Resources/views/backend/_resources/templates/sidebar.html new file mode 100644 index 00000000..8eea299a --- /dev/null +++ b/Resources/views/backend/_resources/templates/sidebar.html @@ -0,0 +1,86 @@ + +
+ + +
+
+
+ + +
diff --git a/Resources/views/backend/adyen_detail/app.js b/Resources/views/backend/adyen_detail/app.js new file mode 100644 index 00000000..4a14c3e3 --- /dev/null +++ b/Resources/views/backend/adyen_detail/app.js @@ -0,0 +1,8 @@ +//{block name="backend/order/application"} +// {$smarty.block.parent} +// {include file="backend/adyen_detail/model/transaction.js"} +// {include file="backend/adyen_detail/store/transaction.js"} +// {include file="backend/adyen_detail/view/adyen_order_detail_data.js"} +// {include file="backend/adyen_detail/view/adyen_order_detail_list.js"} +// {include file="backend/adyen_detail/controller/adyen_order_details_controller.js"} +//{/block} diff --git a/Resources/views/backend/adyen_detail/controller/adyen_order_details_controller.js b/Resources/views/backend/adyen_detail/controller/adyen_order_details_controller.js new file mode 100644 index 00000000..a84c8819 --- /dev/null +++ b/Resources/views/backend/adyen_detail/controller/adyen_order_details_controller.js @@ -0,0 +1,156 @@ +// Adyen order details controller +//{block name="backend/order/controller/detail" append} + +Ext.define('Shopware.apps.AdyenTransaction.controller.OrderDetailsController', { + /** + * Override the order details main controller + * @string + */ + override: 'Shopware.apps.Order.controller.Detail', + + + onTabChange: function () { + let me = this, + captureBtn = Ext.WindowManager.getActive().down('#adyenCaptureBtn'), + cancelBtn = Ext.WindowManager.getActive().down('#adyenCancelBtn'), + refundBtn = Ext.WindowManager.getActive().down('#adyenRefundBtn'); + + // me.callParent will execute the init function of the overridden controller + me.callParent(arguments); + + if (cancelBtn !== null && !cancelBtn.hasListener('click')) { + cancelBtn.on('click', me.cancel.bind(me)); + } + + if (captureBtn !== null && !captureBtn.hasListener('click')) { + captureBtn.on('click', me.capture.bind(me)); + } + + if (refundBtn !== null && !refundBtn.hasListener('click')) { + refundBtn.on('click', me.refund.bind(me)); + } + }, + + capture: function () { + let amount = Ext.WindowManager.getActive().down('#adyenCaptureAmount'), + currencyIso = Ext.WindowManager.getActive().down('#adyenCurrencyIso'), + merchantReference = Ext.WindowManager.getActive().down('#adyenMerchantReference'), + storeId = Ext.WindowManager.getActive().down('#adyenStoreId'), + tab = Ext.WindowManager.getActive().down('#adyen-tab'), + me = this; + + me.loadingMask = new Ext.LoadMask(tab); + me.loadingMask.show(); + + Ext.Ajax.request({ + method: 'GET', + url: '{url controller=AdyenMerchantActions action="capture"}', + params: { + amount: amount.getValue(), + currency: currencyIso.getValue(), + merchantReference: merchantReference.getValue(), + storeId: storeId.getValue() + }, + success: function () { + me.refreshTable(merchantReference, storeId); + }, + failure: function (response, options) { + me.refreshTable(merchantReference, storeId); + Shopware.Notification.createStickyGrowlMessage({ + title: Ext.String.format( + "{s name='notification/adyen/header'}You have new Adyen notifications {/s}", + ), + text: "{s name='notification/adyen/message'}" + response.responseText + "{/s}", + }) + } + }); + }, + + cancel: function () { + let me = this, + merchantReference = Ext.WindowManager.getActive().down('#adyenMerchantReference'), + storeId = Ext.WindowManager.getActive().down('#adyenStoreId'), + tab = Ext.WindowManager.getActive().down('#adyen-tab'); + + me.loadingMask = new Ext.LoadMask(tab); + me.loadingMask.show(); + + Ext.Ajax.request({ + method: 'GET', + url: '{url controller=AdyenMerchantActions action="cancel"}', + params: { + merchantReference: merchantReference.getValue(), + storeId: storeId.getValue() + }, + success: function () { + me.refreshTable(merchantReference, storeId); + }, + failure: function (response, options) { + me.refreshTable(merchantReference, storeId); + Shopware.Notification.createStickyGrowlMessage({ + title: Ext.String.format( + "{s name='notification/adyen/header'}You have new Adyen notifications {/s}", + ), + text: "{s name='notification/adyen/message'}" + response.responseText + "{/s}", + }) + } + }); + }, + + refund: function () { + let me = this, + amount = Ext.WindowManager.getActive().down('#adyenRefundAmount'), + currencyIso = Ext.WindowManager.getActive().down('#adyenCurrencyIso'), + merchantReference = Ext.WindowManager.getActive().down('#adyenMerchantReference'), + storeId = Ext.WindowManager.getActive().down('#adyenStoreId'), + tab = Ext.WindowManager.getActive().down('#adyen-tab'); + + me.loadingMask = new Ext.LoadMask(tab); + me.loadingMask.show(); + + Ext.Ajax.request({ + method: 'GET', + url: '{url controller=AdyenMerchantActions action="refund"}', + params: { + amount: amount.getValue(), + currency: currencyIso.getValue(), + merchantReference: merchantReference.getValue(), + storeId: storeId.getValue() + }, + success: function (response) { + me.refreshTable(merchantReference, storeId); + }, + failure: function (response, options) { + // Handle failed response + me.refreshTable(merchantReference, storeId); + Shopware.Notification.createStickyGrowlMessage({ + title: Ext.String.format( + "{s name='notification/adyen/header'}You have new Adyen notifications {/s}", + ), + text: "{s name='notification/adyen/message'}" + response.responseText + "{/s}", + }) + } + }); + }, + + refreshTable: function (merchantReference, storeId) { + let me = this; + + Ext.Ajax.request({ + method: 'GET', + url: '{url controller=AdyenTransaction action="get"}', + params: { + temporaryId: merchantReference.getValue(), + storeId: storeId.getValue() + }, + success: function (response) { + var list = Ext.WindowManager.getActive().down('adyen-order-detail-list'); + list.getStore().loadData(JSON.parse(response.responseText)); + list.getView().refresh() + me.loadingMask.hide(); + } + }); + } +}); + +//{/block} diff --git a/Resources/views/backend/adyen_detail/model/transaction.js b/Resources/views/backend/adyen_detail/model/transaction.js new file mode 100644 index 00000000..9fa63d08 --- /dev/null +++ b/Resources/views/backend/adyen_detail/model/transaction.js @@ -0,0 +1,32 @@ +//{block name="backend/adyen/transaction"} +Ext.define('Shopware.apps.AdyenTransaction.model.Transaction', { + extend: 'Shopware.data.Model', + + fields: [ + { name : 'pspReference', type: 'string' }, + { name : 'date', type: 'date' }, + { name : 'status', type: 'string' }, + { name : 'paymentMethod', type: 'string' }, + { name : 'eventCode', type: 'string' }, + { name : 'success', type: 'string' }, + { name : 'merchantAccountCode', type: 'string' }, + { name : 'paidAmount', type: 'float' }, + { name : 'amountCurrency', type: 'string' }, + { name : 'refundedAmount', type: 'float' }, + { name : 'viewOnAdyenUrl', type: 'string' }, + { name : 'merchantReference', type: 'string'}, + { name: 'storeId', type: 'string'}, + { name: 'currencyIso', type: 'string'}, + { name: 'captureSupported', type: 'boolean'}, + { name: 'captureAmount', type: 'string'}, + { name: 'partialCapture', type: 'boolean'}, + { name: 'refund', type: 'boolean'}, + { name: 'partialRefund', type: 'boolean'}, + { name: 'refundAmount', type: 'string'}, + { name: 'riskScore', type: 'string'}, + { name: 'capturableAmount', type: 'string'}, + { name: 'refundableAmount', type: 'string'}, + { name: 'cancelSupported', type: 'boolean'} + ] +}); +//{/block} diff --git a/Resources/views/backend/adyen_detail/store/transaction.js b/Resources/views/backend/adyen_detail/store/transaction.js new file mode 100644 index 00000000..efd7cde9 --- /dev/null +++ b/Resources/views/backend/adyen_detail/store/transaction.js @@ -0,0 +1,21 @@ +//{block name="backend/adyen/transaction"} +Ext.define('Shopware.apps.AdyenTransaction.store.Transaction', { + extend:'Shopware.store.Listing', + model: 'Shopware.apps.AdyenTransaction.model.Transaction', + autoLoad: false, + proxy: { + type: 'ajax', + url: '{url controller=AdyenTransaction action="get"}', + reader: { + type: 'json', + root: 'data' + } + }, + configure: function () { + return { + controller: 'AdyenTransaction', + action: 'get' + }; + } +}); +//{/block} diff --git a/Resources/views/backend/adyen_detail/view/adyen_order_detail_data.js b/Resources/views/backend/adyen_detail/view/adyen_order_detail_data.js new file mode 100644 index 00000000..a9924a38 --- /dev/null +++ b/Resources/views/backend/adyen_detail/view/adyen_order_detail_data.js @@ -0,0 +1,292 @@ +//{namespace name=backend/adyen/configuration} + +Ext.define('Shopware.apps.AdyenTransaction.AdyenOrderDetailData', { + extend: 'Ext.container.Container', + record: null, + cls: 'shopware-form', + + initComponent: function () { + var me = this; + + me.items = [ + me.createDetailsContainer() + ]; + + me.store.on('datachanged', function (store, records, options) { + me.record = store.first(); + me.detailsPanel.loadRecord(me.record); + me.query('#adyenPaymentMethod')[0].setSrc(me.record.get('paymentMethod')); + me.query('#captureCurrency')[0].setValue(me.record.get('amountCurrency')); + me.query('#refundCurrency')[0].setValue(me.record.get('amountCurrency')); + + if (!me.record.get('captureSupported') || (parseFloat(me.record.get('capturableAmount')) === 0)) { + me.query('#adyenCaptureToolbar')[0].hide(); + } else { + me.query('#adyenCaptureToolbar')[0].show(); + } + + if (!me.record.get('partialCapture')) { + me.query('#adyenCaptureAmount')[0].hide(); + me.query('#captureCurrency')[0].hide(); + } else { + me.query('#adyenCaptureAmount')[0].show(); + me.query('#captureCurrency')[0].show(); + } + + me.query('#adyenCaptureAmount')[0].maxValue = parseFloat(me.record.get('capturableAmount')); + if (!me.record.get('refund') || (parseFloat(me.record.get('refundableAmount')) === 0)) { + me.query('#adyenRefundToolbar')[0].hide(); + } else { + me.query('#adyenRefundToolbar')[0].show(); + } + + if (!me.record.get('partialRefund')) { + me.query('#adyenRefundAmount')[0].hide(); + me.query('#refundCurrency')[0].hide(); + } else { + me.query('#adyenRefundAmount')[0].show(); + me.query('#refundCurrency')[0].show(); + } + + me.query('#adyenRefundAmount')[0].maxValue = parseFloat(me.record.get('refundableAmount')); + + if (!me.record.get('cancelSupported')) { + me.query('#adyenCancelBtn')[0].hide(); + } else { + me.query('#adyenCancelBtn')[0].show(); + } + }); + + me.callParent(arguments); + }, + + /** + * Creates the container for the detail form panel. + * @return Ext.form.Panel + */ + createDetailsContainer: function () { + var me = this; + + me.detailsPanel = Ext.create('Ext.form.Panel', { + title: '{s name="payment/adyen/detail/transaction"}Transaction{/s}', + titleAlign: 'left', + bodyPadding: 10, + layout: 'anchor', + defaults: { + anchor: '100%' + }, + margin: '10 0', + width: '100%', + items: [ + me.createInnerDetailContainer() + ] + }); + return me.detailsPanel; + }, + + /** + * Creates the outer container for the detail panel which + * has a column layout to display the detail information in two columns. + * + * @return Ext.container.Container + */ + createInnerDetailContainer: function () { + var me = this; + + return Ext.create('Ext.container.Container', { + layout: 'column', + items: [ + me.createDetailElementContainer(me.createLeftDetailElements()), + me.createDetailElementContainer(me.createRightDetailElements()) + ] + }); + }, + + /** + * Creates the column container for the detail elements which displayed + * in two columns. + * + * @param { Array } items - The container items. + */ + createDetailElementContainer: function (items) { + return Ext.create('Ext.container.Container', { + columnWidth: 0.5, + defaults: { + xtype: 'displayfield', + labelWidth: 155 + }, + items: items + }); + }, + + /** + * Creates the elements for the left column container which displays the + * fields in two columns. + * + * @return array - Contains the form fields + */ + createLeftDetailElements: function () { + var me = this, fields; + fields = [ + { name: 'pspReference', fieldLabel: '{s name="payment/adyen/detail/pspreference"}PSP Reference{/s}'}, + { name: 'date', fieldLabel: '{s name="payment/adyen/detail/date"}Date{/s}'}, + { name: 'eventCode', fieldLabel: '{s name="payment/adyen/detail/eventcode"}Event code{/s}'}, + { name: 'merchantAccountCode', fieldLabel: '{s name="payment/adyen/detail/merchant"}Merchant{/s}'}, + { + xtype: 'toolbar', + itemId: 'adyenCaptureToolbar', + style: { + 'background-color': 'rgb(240, 242, 244)' + }, + items: [ + { + xtype: 'numberfield', + hideTrigger: true, + minValue: 0, + name: 'capturableAmount', + itemId: 'adyenCaptureAmount', + fieldLabel: '', + allowBlank: false, + width: 250, + forcePrecision: true, + decimalPrecision: 3 + }, + { + xtype: 'displayfield', + itemId: 'captureCurrency', + name: 'captureCurrency', + margin: '0 10 0 0' + }, + { + action: 'capturePayment', + xtype: 'button', + itemId: 'adyenCaptureBtn', + cls: 'primary', + text: 'Capture', + margin: '10 0 0 0', + width: 105 + } + ] + }, + { + xtype: 'toolbar', + itemId: 'adyenRefundToolbar', + style: { + 'background-color': 'rgb(240, 242, 244)' + }, + items: [ + { + xtype: 'numberfield', + hideTrigger: true, + minValue: 0, + name: 'refundableAmount', + itemId: 'adyenRefundAmount', + fieldLabel: '', + allowBlank: false, + width: 250, + forcePrecision: true, + decimalPrecision: 3 + }, + { + xtype: 'displayfield', + itemId: 'refundCurrency', + name: 'refundCurrency', + margin: '0 10 0 0' + }, + { + action: 'refundPayment', + xtype: 'button', + itemId: 'adyenRefundBtn', + cls: 'primary', + text: '{s name="payment/adyen/detail/refund"}Refund{/s}', + margin: '10 0 0 0', + width: 105 + } + ] + }, + { + xtype: 'toolbar', + style: { + 'background-color': 'rgb(240, 242, 244)' + }, + items: [ + { + action: 'cancelPayment', + xtype: 'button', + itemId: 'adyenCancelBtn', + cls: 'primary', + text: '{s name="payment/adyen/detail/cancel"}Cancel{/s}', + margin: '10 10 0 0', + width: 105 + }, + { + action: 'viewOnAdyen', + xtype: 'button', + cls: 'secondary', + text: '{s name="payment/adyen/detail/viewonadyenca"}View payment on Adyen CA{/s}', + margin: '10 0 0 0', + handler: function () { + window.open(me.record.get('viewOnAdyenUrl') ); + } + } + ] + } + ]; + return fields; + }, + + /** + * Creates the elements for the right column container which displays the + * fields in two columns. + * + * @return Array - Contains the form fields + */ + createRightDetailElements: function () { + var me = this; + + return [ + { + xtype: 'panel', + layout: 'hbox', + border: false, + height: 30, + items: [ + { + xtype: 'displayfield', + labelWidth: 155, + fieldLabel: '{s name="payment/adyen/detail/paymentmethod"}Payment method{/s}', + height: 30 + }, + { + xtype: 'image', + itemId: 'adyenPaymentMethod', + name: 'paymentMethod', + src: '', + height: 30 + }, + ] + }, + { name: 'amountCurrency', fieldLabel: '{s name="payment/adyen/detail/currency"}Currency{/s}'}, + { name: 'status', fieldLabel: '{s name="payment/adyen/detail/status"}Status{/s}'}, + { name: 'success', fieldLabel: '{s name="payment/adyen/detail/success"}Success{/s}'}, + { name: 'riskScore', fieldLabel: '{s name="payment/adyen/detail/riskscore"}Risk score{/s}'}, + { name: 'paidAmount', fieldLabel: '{s name="payment/adyen/detail/paidamount"}Paid amount{/s}'}, + { name: 'refundedAmount', fieldLabel: '{s name="payment/adyen/detail/refundedamount"}Refunded amount{/s}'}, + { + xtype: 'hidden', + itemId: 'adyenMerchantReference', + name: 'merchantReference' + }, + { + xtype: 'hidden', + itemId: 'adyenStoreId', + name: 'storeId', + }, + { + xtype: 'hidden', + itemId: 'adyenCurrencyIso', + name: 'currencyIso' + } + ]; + }, +}); diff --git a/Resources/views/backend/adyen_detail/view/adyen_order_detail_list.js b/Resources/views/backend/adyen_detail/view/adyen_order_detail_list.js new file mode 100644 index 00000000..f271d055 --- /dev/null +++ b/Resources/views/backend/adyen_detail/view/adyen_order_detail_list.js @@ -0,0 +1,36 @@ +//{namespace name=backend/adyen/configuration} + +Ext.define('Shopware.apps.AdyenTransaction.AdyenOrderDetailList', { + extend: 'Ext.grid.Panel', + alias: 'widget.adyen-order-detail-list', + width: '100%', + + initComponent: function () { + var me = this; + + me.columns = me.getColumns(); + + me.callParent(arguments); + }, + + getColumns: function () { + return [{ + header: '{s name="payment/adyen/detail/datetime"}Date & time{/s}', + dataIndex: 'date', + sortable: false, + xtype: 'datecolumn', + format: 'd.m.Y H:i:s', + flex: 1, + }, { + header: '{s name="payment/adyen/detail/eventcode"}Event code{/s}', + dataIndex: 'eventCode', + sortable: false, + flex: 1, + }, { + header: '{s name="payment/adyen/detail/status"}Status{/s}', + dataIndex: 'status', + sortable: false, + flex: 1, + }]; + } +}); diff --git a/Resources/views/backend/adyen_detail/view/window.js b/Resources/views/backend/adyen_detail/view/window.js new file mode 100644 index 00000000..ed5c7e48 --- /dev/null +++ b/Resources/views/backend/adyen_detail/view/window.js @@ -0,0 +1,72 @@ +//{block name="backend/order/view/detail/window"} +// {$smarty.block.parent} +Ext.define('Shopware.apps.AdyenTransaction.Window', { + override: 'Shopware.apps.Order.view.detail.Window', + + initComponent: function () { + var me = this; + me.callParent(); + }, + + createTabPanel: function () { + let me = this, + result = me.callParent(); + + result.add(me.createAdyenTab(!!me.record.raw.adyenTransaction)); + + return result; + }, + + createAdyenTab: function (enableTab) { + var me = this; + + var transactionStore = Ext.create('Shopware.apps.AdyenTransaction.store.Transaction'); + + let items = []; + if (enableTab) { + items.push( + Ext.create('Shopware.apps.AdyenTransaction.AdyenOrderDetailData', { + store: transactionStore, + layout: { + type: 'vbox', + align: 'stretch' + }, + region: 'north' + }), + Ext.create('Shopware.apps.AdyenTransaction.AdyenOrderDetailList', { + region: 'center', + store: transactionStore, + record: me.record + }) + ); + } + + me.adyenTransactionTab = Ext.create('Ext.container.Container', { + title: 'Adyen', + itemId: 'adyen-tab', + layout: 'border', + items: items, + disabled: !enableTab + }); + + me.loadingMask = new Ext.LoadMask(me.adyenTransactionTab); + me.adyenTransactionTab.addListener('activate', function () { + me.loadingMask.show(); + Ext.Ajax.request({ + method: 'GET', + url: '{url controller=AdyenTransaction action="get"}', + params: { + temporaryId: me.record.get('temporaryId'), + storeId: me.record.get('shopId') + }, + success: function (response) { + transactionStore.loadData(JSON.parse(response.responseText)); + me.loadingMask.hide(); + } + }); + }, me); + + return me.adyenTransactionTab; + } +}); +//{/block} diff --git a/Resources/views/backend/adyen_list/adyen_order_list.js b/Resources/views/backend/adyen_list/adyen_order_list.js new file mode 100644 index 00000000..212bbd83 --- /dev/null +++ b/Resources/views/backend/adyen_list/adyen_order_list.js @@ -0,0 +1,51 @@ +// {namespace name=backend/adyen/configuration} + +//{block name="backend/order/view/list/list"} +// {$smarty.block.parent} + +Ext.define('Shopware.apps.Adyen.view.Order.List', { + override: 'Shopware.apps.Order.view.list.List', + getColumns: function () { + let result = this.callParent(); + + result = this.addAdyenColumns(result); + + return result; + }, + addAdyenColumns: function (columns) { + let i = 0; + for (i; i < columns.length; i++) { + if (columns[i].dataIndex === 'number') { + break; + } + } + + columns.splice(i + 1, 0, { + header: '{s name="order/adyen/psp"}Adyen PSP reference{/s}', + dataIndex: 'adyenPspReference', + flex: 4, + sortable: false, + renderer: renderAdyenPspReferenceColumn + }); + + columns.splice(i + 2, 0, { + header: '{s name="order/adyen/paymentMethod"}Adyen payment method{/s}', + dataIndex: 'adyenPaymentMethod', + flex: 1, + sortable: false, + renderer: renderPrintLabelsColumn + }); + + return columns; + + function renderAdyenPspReferenceColumn(value, meta, model) { + return model.get('adyenPspReference'); + } + + function renderPrintLabelsColumn(value, meta, model) { + return model.get('adyenPaymentMethod'); + } + } +}); + +//{/block} diff --git a/Resources/views/backend/adyen_list/models/adyen_order_model.js b/Resources/views/backend/adyen_list/models/adyen_order_model.js new file mode 100644 index 00000000..a5358f41 --- /dev/null +++ b/Resources/views/backend/adyen_list/models/adyen_order_model.js @@ -0,0 +1,7 @@ +//{block name="backend/order/model/order/fields"} +// {$smarty.block.parent} + +{ name : 'adyenPspReference', type : 'string' }, +{ name : 'adyenPaymentMethod', type : 'string', defaultValue : ''}, + +//{/block} diff --git a/Resources/views/backend/adyen_payment_main/index.tpl b/Resources/views/backend/adyen_payment_main/index.tpl new file mode 100644 index 00000000..b1d3b7a9 --- /dev/null +++ b/Resources/views/backend/adyen_payment_main/index.tpl @@ -0,0 +1,84 @@ +{namespace name=backend/adyen/configuration} +{extends file="parent:backend/_base/layout.tpl"} +{block name="styles"} + + +{/block} +{block name="scripts"} + + + + + + + + + + + + + + + + + + +{/block} +{block name="content/main"} +
+ +
+
+
+
+
    +
  • +
  • +
  • +
  • +
+
+
+
+
+
+
+
+
+ +
+
+
+{/block} +{block name="content/javascript"} + +{/block} diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js deleted file mode 100755 index 76020f0a..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js +++ /dev/null @@ -1,29 +0,0 @@ - -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension', { - extend: 'Enlight.app.SubApplication', - - name:'Shopware.apps.AdyenPaymentNotificationsListingExtension', - - loadPath: '{url action=load}', - bulkLoad: true, - - controllers: [ 'Main' ], - - views: [ - 'list.Window', - 'list.Notification', - 'list.extensions.NotificationFilter', - ], - - models: [ - 'Notification', - ], - - stores: [ - 'Notification', - ], - - launch: function () { - return this.getController('Main').mainWindow; - } -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/controller/main.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/controller/main.js deleted file mode 100755 index 1c160ee8..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/controller/main.js +++ /dev/null @@ -1,11 +0,0 @@ - -// {namespace name=backend/adyen/notification/listing} -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.controller.Main', { - extend: 'Enlight.app.Controller', - - init: function () { - var me = this; - - me.mainWindow = me.getView('list.Window').create({ }).show(); - }, -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js deleted file mode 100755 index efe2d4df..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js +++ /dev/null @@ -1,41 +0,0 @@ - -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.model.Notification', { - extend: 'Shopware.data.Model', - - configure: function () { - return { - controller: 'AdyenPaymentNotificationsListingExtension', - }; - }, - - fields: [ - { name : 'id', type: 'int', useNull: true }, - { name : 'pspReference', type: 'string' }, - { name : 'createdAt', type: 'date' }, - { name : 'updatedAt', type: 'date' }, - { name : 'status', type: 'string' }, - { name : 'paymentMethod', type: 'string' }, - { name : 'eventCode', type: 'string' }, - { name : 'success', type: 'string' }, - { name : 'merchantAccountCode', type: 'string' }, - { name : 'amountValue', type: 'float', convert: function (v) { - return v / 100; - } }, - { name : 'amountCurrency', type: 'string' }, - { name : 'errorDetails', type: 'string' }, - { name : 'orderId', type: 'int' }, - ], - - associations: [ - { - relation: 'ManyToOne', - field: 'orderId', - type: 'hasMany', - model: 'Shopware.apps.Order.model.Order', - name: 'getOrder', - associationKey: 'id' - }, - ] - -}); - diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/store/notification.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/store/notification.js deleted file mode 100755 index 86123682..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/store/notification.js +++ /dev/null @@ -1,11 +0,0 @@ - -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.store.Notification', { - extend:'Shopware.store.Listing', - - configure: function () { - return { - controller: 'AdyenPaymentNotificationsListingExtension' - }; - }, - model: 'Shopware.apps.AdyenPaymentNotificationsListingExtension.model.Notification' -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/extensions/notification_filter.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/extensions/notification_filter.js deleted file mode 100644 index 054ea8ea..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/extensions/notification_filter.js +++ /dev/null @@ -1,59 +0,0 @@ -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.view.list.extensions.NotificationFilter', { - extend: 'Shopware.listing.FilterPanel', - alias: 'widget.notification-listing-filter-panel', - width: 270, - controller: 'AdyenPaymentNotificationsListingExtension', - - configure: function () { - var me = this; - - return { - controller: me.controller, - model: 'Shopware.apps.AdyenPaymentNotificationsListingExtension.model.Notification', - fields: { - createdAt: { }, - updatedAt: { }, - status: { - xtype: 'combobox', - displayField: 'status', - valueField: 'status', - store: new Ext.data.Store({ - autoLoad: true, - proxy: { - type: 'ajax', - url: window.location.href.substr(0, window.location.href.indexOf('backend')) + 'backend/' + me.controller + '/getNotificationStatusses', - reader: { - type: 'json', - root: 'statusses' - } - }, - fields: [ - 'status' - ] - }), - fieldLabel: '{s name="column/status"}Status{/s}', - }, - eventCode: { - xtype: 'combobox', - displayField: 'eventCode', - valueField: 'eventCode', - store: new Ext.data.Store({ - autoLoad: true, - proxy: { - type: 'ajax', - url: window.location.href.substr(0, window.location.href.indexOf('backend')) + 'backend/' + me.controller + '/getEventCodes', - reader: { - type: 'json', - root: 'eventCodes' - } - }, - fields: [ - 'eventCode' - ] - }), - fieldLabel: '{s name="column/eventCode"}Event Code{/s}', - }, - } - }; - }, -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/notification.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/notification.js deleted file mode 100755 index 191eac70..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/notification.js +++ /dev/null @@ -1,101 +0,0 @@ - -// {namespace name=backend/adyen/notification/listing} -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.view.list.Notification', { - extend: 'Shopware.grid.Panel', - alias: 'widget.product-listing-grid', - region: 'center', - - configure: function () { - return { - addButton: false, - deleteButton: false, - deleteColumn: false, - editColumn: false, - columns: { - 'pspReference': { }, - 'createdAt': { }, - 'updatedAt': { }, - 'status': { }, - 'paymentMethod': { }, - 'eventCode': { }, - 'success': { }, - 'merchantAccountCode': { }, - 'amountValue': { }, - 'amountCurrency': { }, - 'errorDetails': { }, - 'orderId': { - renderer: this.orderIdRenderer - }, - } - }; - }, - - orderIdRenderer: function (value, styles, row) { - return !Ext.isEmpty(row.raw.order) ? row.raw.order.number : ''; - }, - - - /** - * Contains all snippets for the view component - * @object - */ - snippets:{ - columns: { - pspReference:'{s name="column/pspReference"}PSP Reference{/s}', - createdAt:'{s name="column/createdAt"}Created at{/s}', - updatedAt:'{s name="column/updatedAt"}Updated at{/s}', - status:'{s name="column/status"}Status{/s}', - paymentMethod:'{s name="column/paymentMethod"}Payment method{/s}', - eventCode:'{s name="column/eventCode"}Event Code{/s}', - success:'{s name="column/success"}Success{/s}', - merchantAccountCode:'{s name="column/merchantAccountCode"}Merchant Account Code{/s}', - amountValue:'{s name="column/amountValue"}Amount Value{/s}', - amountCurrency:'{s name="column/amountCurrency"}Amount Currency{/s}', - errorDetails:'{s name="column/errorDetails"}Error Details{/s}', - orderDetails:'{s name="column/orderDetails"}Order Details{/s}', - }, - successTitle: '{s name="message/save/success_title"}Successful{/s}', - failureTitle: '{s name="message/save/error_title"}Error{/s}', - orderDoesNotExistAnymore: '{s name="order_does_not_exist_anymore"}This order does not exist anymore{/s}', - }, - - createActionColumnItems: function() { - var me = this, items; - items = me.callParent(arguments); - items.push(me.createEditOrderColumn()); - - return items; - }, - - createEditOrderColumn: function () { - var me = this; - - return { - iconCls: 'sprite-eye', - action: 'editOrder', - tooltip: me.snippets.columns.orderDetails, - - isDisabled: function (view, rowIndex, colIndex, item, record) { - return Ext.isEmpty(record.raw.order); - }, - handler: function (view, rowIndex, colIndex, item) { - var store = view.getStore(), - record = store.getAt(rowIndex); - - if (Ext.isEmpty(record.raw.order)) { - Shopware.Msg.createGrowlMessage(me.snippets.failureTitle, me.snippets.orderDoesNotExistAnymore); - - return; - } - - Shopware.app.Application.addSubApplication({ - name: 'Shopware.apps.Order', - action: 'detail', - params: { - orderId: record.data.orderId - } - }); - } - } - }, -}); diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/window.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/window.js deleted file mode 100755 index 087980a0..00000000 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/window.js +++ /dev/null @@ -1,18 +0,0 @@ - -Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.view.list.Window', { - extend: 'Shopware.window.Listing', - alias: 'widget.product-list-window', - height: 450, - title : '{s name="window_title"}Notification listing{/s}', - - configure: function () { - return { - listingGrid: 'Shopware.apps.AdyenPaymentNotificationsListingExtension.view.list.Notification', - listingStore: 'Shopware.apps.AdyenPaymentNotificationsListingExtension.store.Notification', - - extensions: [ - { xtype: 'notification-listing-filter-panel' } - ] - }; - } -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/app.js b/Resources/views/backend/adyen_payment_order/app.js deleted file mode 100644 index 1636470e..00000000 --- a/Resources/views/backend/adyen_payment_order/app.js +++ /dev/null @@ -1,10 +0,0 @@ -//{block name="backend/order/application"} -// {$smarty.block.parent} -// {include file="backend/adyen_payment_order/view/detail/transaction_details.js"} -// {include file="backend/adyen_payment_order/view/detail/transaction_tabs.js"} -// {include file="backend/adyen_payment_order/view/detail/tabs/notifications.js"} -// {include file="backend/adyen_payment_order/view/detail/tabs/refunds.js"} -// {include file="backend/adyen_payment_order/view/detail/tabs/notifications/list.js"} -// {include file="backend/adyen_payment_order/view/detail/tabs/notifications/detail.js"} -// {include file="backend/adyen_payment_order/view/detail/tabs/refunds/detail.js"} -//{/block} \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/model/order.js b/Resources/views/backend/adyen_payment_order/model/order.js deleted file mode 100644 index f9bc4aa2..00000000 --- a/Resources/views/backend/adyen_payment_order/model/order.js +++ /dev/null @@ -1,6 +0,0 @@ -// - -//{block name="backend/order/model/order/fields"} -//{$smarty.block.parent} -{ name : 'adyenRefundable', type: 'boolean' }, -//{/block} \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications.js b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications.js deleted file mode 100644 index e40da291..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications.js +++ /dev/null @@ -1,53 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.Notifications', { - extend: 'Ext.container.Container', - - layout: 'border', - formActions: {}, - - initComponent: function () { - var me = this; - me.items = me.getNotifications(); - me.callParent(arguments); - }, - - getNotifications: function () { - var me = this; - - return [ - Ext.create('Ext.container.Container', { - layout: 'border', - region: 'center', - items: [ - me.getWidgetList(), - me.getWidgetDetail() - ] - }) - ]; - }, - - getWidgetList: function () { - var me = this; - - me.listView = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.notifications.List', { - store: me.store, - notifications: me, - flex: 1, - region: 'west' - }); - return me.listView; - }, - - getWidgetDetail: function () { - var me = this; - - me.detailView = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.notifications.Detail', { - flex: 2, - region: 'center' - }); - - me.detailView.disable(); - return me.detailView; - }, -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/detail.js b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/detail.js deleted file mode 100644 index 4ddc6bae..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/detail.js +++ /dev/null @@ -1,41 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.notifications.Detail', { - extend: 'Ext.form.Panel', - layout: 'anchor', - - initComponent: function () { - var me = this; - - me.items = me.getItems(); - me.callParent(arguments); - - if (me.store && me.store.first()) { - me.loadRecord(me.store.first()); - } - }, - - getItems: function () { - return [Ext.create('Ext.container.Container', { - columnWidth: 0.5, - padding: 10, - defaults: { - xtype: 'displayfield', - labelWidth: 155 - }, - items: [ - { name: 'pspReference', fieldLabel: 'PSP Reference'}, - { name: 'createdAt', fieldLabel: 'Created at'}, - { name: 'updatedAt', fieldLabel: 'Updated at'}, - { name: 'eventCode', fieldLabel: 'Event code'}, - { name: 'merchantAccountCode', fieldLabel: 'Merchant'}, - { name: 'paymentMethod', fieldLabel: 'Payment method'}, - { name: 'amountValue', fieldLabel: 'Amount'}, - { name: 'amountCurrency', fieldLabel: 'Currency'}, - { name: 'status', fieldLabel: 'Status'}, - { name: 'success', fieldLabel: 'Success'}, - { name: 'errorDetails', fieldLabel: 'Error Details'}, - ] - })]; - } -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/list.js b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/list.js deleted file mode 100644 index 15cc6429..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/list.js +++ /dev/null @@ -1,41 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.notifications.List', { - extend: 'Ext.grid.Panel', - - initComponent: function () { - var me = this; - - me.columns = me.getColumns(); - - me.store.sort([{ - property : 'id', - direction: 'DESC' - }]); - - me.getSelectionModel().on('selectionchange', function (row, selected, options) { - me.notifications.detailView.loadRecord(selected[0]); - me.notifications.detailView.enable(); - }); - - me.callParent(arguments); - }, - - getColumns: function () { - return [{ - header: 'Date & time', - dataIndex: 'createdAt', - sortable: false, - xtype:'datecolumn', - format:'d.m.Y H:i:s', - }, { - header: 'Event code', - dataIndex: 'eventCode', - sortable: false, - }, { - header: 'Status', - dataIndex: 'status', - sortable: false, - }]; - } -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds.js b/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds.js deleted file mode 100644 index ae55c6d9..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds.js +++ /dev/null @@ -1,29 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.Refunds', { - extend: 'Ext.container.Container', - - initComponent: function () { - var me = this; - me.items = me.createItems(); - me.callParent(arguments); - }, - - createItems: function () { - var me = this; - - return [ - me.getRefundsDetail() - ]; - }, - - getRefundsDetail: function () { - var me = this; - - me.detailView = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.refunds.Detail', { - record: me.record, - refunds: me, - }); - return me.detailView; - } -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds/detail.js b/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds/detail.js deleted file mode 100644 index 508f986d..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds/detail.js +++ /dev/null @@ -1,96 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.refunds.Detail', { - extend: 'Ext.form.Panel', - - layout: { - type: 'table', - columns: 2, - }, - bodyPadding: 10, - height: '100%', - autoScroll: true, - ui: 'footer', - - initComponent: function () { - var me = this; - me.items = me.createItems(); - me.dockedItems = me.createDock(); - me.callParent(arguments); - - me.loadRecord(me.record); - }, - - createItems: function () { - var me = this, - fields = []; - - fields.push({ - xtype: 'label', - text: 'Order amount', - width: 200 - }); - fields.push({ - xtype: 'displayfield', - name: 'invoiceAmount', - }); - - fields.push({ - xtype: 'label', - text: 'Total refund amount', - }); - fields.push({ - xtype: 'displayfield', - value: me.record.raw.adyenNotification.amountValue, - }); - - fields.push({ - xtype: 'label', - text: 'Currency', - }); - fields.push({ - xtype: 'displayfield', - value: me.record.raw.adyenNotification.amountCurrency, - }); - - return fields; - }, - - createDock: function () { - var me = this, - items = []; - - items.push({ - type: 'button', - text: 'Full refund', - cls: 'primary', - handler: function () { - me.up('window').setLoading(true); - - Ext.Ajax.request({ - url: '{url controller="AdyenPaymentRefund" action="refund"}', - params: { - orderId: me.record.get('id') - }, - success: function (response) { - var json = JSON.parse(response.responseText); - me.up('window').setLoading(false); - Ext.Msg.alert( - 'Adyen Refund', - 'A refund with Reference ID ' - + json.refundReference - + ' has been created. Check again in a few minutes to see if it has succeeded.', - Ext.emptyFn - ); - } - }); - } - }); - - return [{ - xtype: 'toolbar', - dock: 'bottom', - items: items - }]; - }, -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/transaction_details.js b/Resources/views/backend/adyen_payment_order/view/detail/transaction_details.js deleted file mode 100644 index 60e6cb4b..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/transaction_details.js +++ /dev/null @@ -1,119 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionDetails', { - extend: 'Ext.container.Container', - title: 'Transaction', - record: null, - - initComponent: function () { - var me = this; - - me.items = [ - me.createDetailsContainer() - ]; - - me.store.on('load', function (store, records, options ) { - me.record = store.first(); - me.detailsPanel.loadRecord(me.record); - }); - - me.callParent(arguments); - }, - - /** - * Creates the container for the detail form panel. - * @return Ext.form.Panel - */ - createDetailsContainer: function () { - var me = this; - - me.detailsPanel = Ext.create('Ext.form.Panel', { - title: 'Latest notification', - titleAlign: 'left', - bodyPadding: 10, - layout: 'anchor', - defaults: { - anchor: '100%' - }, - margin: '10 0', - items: [ - me.createInnerDetailContainer() - ] - }); - return me.detailsPanel; - }, - - /** - * Creates the outer container for the detail panel which - * has a column layout to display the detail information in two columns. - * - * @return Ext.container.Container - */ - createInnerDetailContainer: function () { - var me = this; - - return Ext.create('Ext.container.Container', { - layout: 'column', - items: [ - me.createDetailElementContainer(me.createLeftDetailElements()), - me.createDetailElementContainer(me.createRightDetailElements()) - ] - }); - }, - - /** - * Creates the column container for the detail elements which displayed - * in two columns. - * - * @param { Array } items - The container items. - */ - createDetailElementContainer: function (items) { - return Ext.create('Ext.container.Container', { - columnWidth: 0.5, - defaults: { - xtype: 'displayfield', - labelWidth: 155 - }, - items: items - }); - }, - - /** - * Creates the elements for the left column container which displays the - * fields in two columns. - * - * @return array - Contains the form fields - */ - createLeftDetailElements: function () { - var me = this, fields; - fields = [ - { name: 'pspReference', fieldLabel: 'PSP Reference'}, - { name: 'createdAt', fieldLabel: 'Created at'}, - { name: 'updatedAt', fieldLabel: 'Updated at'}, - { name: 'eventCode', fieldLabel: 'Event code'}, - { name: 'merchantAccountCode', fieldLabel: 'Merchant'}, - - ]; - return fields; - }, - - /** - * Creates the elements for the right column container which displays the - * fields in two columns. - * - * @return Array - Contains the form fields - */ - createRightDetailElements: function () { - var me = this; - - return [ - { name: 'paymentMethod', fieldLabel: 'Payment method'}, - { name: 'amountValue', fieldLabel: 'Amount'}, - { name: 'amountCurrency', fieldLabel: 'Currency'}, - { name: 'status', fieldLabel: 'Status'}, - { name: 'success', fieldLabel: 'Success'}, - { name: 'errorDetails', fieldLabel: 'Error Details'}, - ]; - }, - -}); diff --git a/Resources/views/backend/adyen_payment_order/view/detail/transaction_tabs.js b/Resources/views/backend/adyen_payment_order/view/detail/transaction_tabs.js deleted file mode 100644 index e994de6f..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/transaction_tabs.js +++ /dev/null @@ -1,50 +0,0 @@ -// - -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionTabs', { - extend: 'Ext.tab.Panel', - - border: 0, - bodyBorder: false, - tabBarPosition: 'bottom', - - initComponent: function () { - var me = this; - me.callParent(arguments); - - me.createItems(); - }, - - defaults: { - styleHtmlContent: true - }, - - createItems: function () { - var me = this; - me.add(me.createNotificationsTab()); - me.add(me.createRefundsTab()); - - me.doLayout(); - me.setActiveTab(0); - }, - - createNotificationsTab: function () { - var me = this; - me.tabNotifications = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.Notifications', { - title: 'Notifications', - record: me.record, - store: me.store, - }); - return me.tabNotifications; - }, - - createRefundsTab: function () { - var me = this; - me.tabRefunds = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.tabs.Refunds', { - title: 'Refunds', - record: me.record, - store: me.store, - disabled: !me.record.get('adyenRefundable') - }); - return me.tabRefunds; - }, -}); \ No newline at end of file diff --git a/Resources/views/backend/adyen_payment_order/view/detail/window.js b/Resources/views/backend/adyen_payment_order/view/detail/window.js deleted file mode 100644 index 430119f3..00000000 --- a/Resources/views/backend/adyen_payment_order/view/detail/window.js +++ /dev/null @@ -1,77 +0,0 @@ -//{block name="backend/order/view/detail/window"} -// {$smarty.block.parent} -Ext.define('Shopware.apps.AdyenPaymentOrder.view.detail.Window', { - /** - * Override the customer detail window - * @string - */ - override: 'Shopware.apps.Order.view.detail.Window', - - initComponent: function () { - var me = this; - me.callParent(); - }, - - /** - * Overwrite to add adyen transaction tab if necessary - */ - createTabPanel: function () { - var me = this, - result = me.callParent(); - - result.add(me.createAdyenTab(!!me.record.raw.adyenTransaction)); - - return result; - }, - - /** - * Generate Adyen Tab - */ - createAdyenTab: function (enableTab) { - var me = this; - - var transactionStore = Ext.create('Shopware.apps.AdyenPaymentNotificationsListingExtension.store.Notification'); - - let items = []; - if (enableTab) { - items.push( - Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionDetails', { - store: transactionStore, - layout: { - type: 'vbox', - align: 'stretch' - }, - region: 'north' - }), - Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionTabs', { - region: 'center', - store: transactionStore, - record: me.record - }) - ); - } - - me.adyenTransactionTab = Ext.create('Ext.container.Container', { - title: 'Adyen Notifications', - layout: 'border', - items: items, - disabled: !enableTab - }); - - me.adyenTransactionTab.addListener('activate', function () { - transactionStore.load({ - params: { - filter: JSON.stringify([{ - property: "orderId", - value: me.record.get('id'), - operator: null, - expression: '=' - }]) - } - }); - }, me); - - return me.adyenTransactionTab; - } -}); -//{/block} diff --git a/Resources/views/backend/customer/adyen_payment_method/app.js b/Resources/views/backend/customer/adyen_payment_method/app.js deleted file mode 100644 index b27ff47d..00000000 --- a/Resources/views/backend/customer/adyen_payment_method/app.js +++ /dev/null @@ -1,4 +0,0 @@ -//{block name="backend/customer/application"} - //{$smarty.block.parent} - //{include file="backend/customer/adyen_payment_method/view/list.js"} -//{/block} \ No newline at end of file diff --git a/Resources/views/backend/customer/adyen_payment_method/view/list.js b/Resources/views/backend/customer/adyen_payment_method/view/list.js deleted file mode 100644 index 954ad8e6..00000000 --- a/Resources/views/backend/customer/adyen_payment_method/view/list.js +++ /dev/null @@ -1,22 +0,0 @@ -// -// {namespace name=backend/customer/view/order} - -// {block name="backend/customer/view/order/list"} - Ext.define('Shopware.apps.Customer.AdyenPayment.view.List', { - override: 'Shopware.apps.Customer.view.order.List', - - getColumns: function () { - var columns = this.callParent(arguments); - columns.splice(2, 0, { - header: 'Adyen payment', - dataIndex: 'adyen_payment_order_payment', - flex:1, - sortable: false, - renderer: function (value, metaData, record) { - return record.raw.adyen_payment_order_payment; - } - }); - return columns; - }, - }); -//{/block} \ No newline at end of file diff --git a/Resources/views/backend/index/adyen_header.tpl b/Resources/views/backend/index/adyen_header.tpl new file mode 100644 index 00000000..9ea76b2d --- /dev/null +++ b/Resources/views/backend/index/adyen_header.tpl @@ -0,0 +1,9 @@ +{block name="backend/base/header/css" append} + +{/block} diff --git a/Resources/views/backend/order/adyen_payment_method/app.js b/Resources/views/backend/order/adyen_payment_method/app.js deleted file mode 100644 index f85d467d..00000000 --- a/Resources/views/backend/order/adyen_payment_method/app.js +++ /dev/null @@ -1,4 +0,0 @@ -//{block name="backend/order/application"} - //{$smarty.block.parent} - //{include file="backend/order/adyen_payment_method/view/list.js"} -//{/block} \ No newline at end of file diff --git a/Resources/views/backend/order/adyen_payment_method/view/list.js b/Resources/views/backend/order/adyen_payment_method/view/list.js deleted file mode 100644 index b7f00aaf..00000000 --- a/Resources/views/backend/order/adyen_payment_method/view/list.js +++ /dev/null @@ -1,22 +0,0 @@ -// -// {namespace name=backend/order/main} - -// {block name="backend/order/view/list/list"} - Ext.define('Shopware.apps.Order.AdyenPayment.view.List', { - override: 'Shopware.apps.Order.view.list.List', - - getColumns: function () { - var columns = this.callParent(arguments); - columns.splice(2, 0, { - header: 'Adyen payment', - dataIndex: 'adyen_payment_order_payment', - flex:1, - sortable: false, - renderer: function (value, metaData, record) { - return record.raw.adyen_payment_order_payment; - } - }); - return columns; - }, - }); -//{/block} \ No newline at end of file diff --git a/Resources/views/frontend/adyen_payment_process/index.tpl b/Resources/views/frontend/adyen_payment_process/index.tpl new file mode 100644 index 00000000..b5c3f8c4 --- /dev/null +++ b/Resources/views/frontend/adyen_payment_process/index.tpl @@ -0,0 +1,33 @@ +{extends file="frontend/checkout/confirm.tpl"} + +{block name='frontend_index_content_left'} + {if !$theme.checkoutHeader} + {$smarty.block.parent} + {/if} +{/block} + +{block name="frontend_index_after_body"} + {$smarty.block.parent} + + {include file="frontend/checkout/adyen_libaries.tpl"} +{/block} + +{* Main content *} +{block name="frontend_index_content"} +
+ + +
 
+{/block} diff --git a/Resources/views/frontend/checkout/adyen_configuration.tpl b/Resources/views/frontend/checkout/adyen_configuration.tpl deleted file mode 100644 index 6cd0cca3..00000000 --- a/Resources/views/frontend/checkout/adyen_configuration.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-
diff --git a/Resources/views/frontend/checkout/adyen_libaries.tpl b/Resources/views/frontend/checkout/adyen_libaries.tpl index 9758ed7b..fffb7ba6 100644 --- a/Resources/views/frontend/checkout/adyen_libaries.tpl +++ b/Resources/views/frontend/checkout/adyen_libaries.tpl @@ -1,11 +1,10 @@ {block name='frontend_checkout_payment_content_adyen_libaries'} - {if $sAdyenGoogleConfig} - - {/if} - - + + {/block} diff --git a/Resources/views/frontend/checkout/cart.tpl b/Resources/views/frontend/checkout/cart.tpl new file mode 100755 index 00000000..398ac690 --- /dev/null +++ b/Resources/views/frontend/checkout/cart.tpl @@ -0,0 +1,45 @@ +{extends file='parent:frontend/checkout/cart.tpl'} + +{block name="frontend_index_after_body"} + {$smarty.block.parent} + + {if $adyenShowExpressCheckout } + {include file="frontend/checkout/adyen_libaries.tpl"} + + {/if} + +{/block} + +{* Spike test Express Checkout integration *} +{block name='frontend_checkout_cart_table_actions_bottom'} + {$smarty.block.parent} + + {if $adyenShowExpressCheckout } + {block name='adyen_frontend_detail_express_buy_button'} +
+
+
+ {assign var="adyenExpressCheckoutPaymentTypes" value=['applepay', 'amazonpay', 'paywithgoogle', 'paypal']} + {foreach $adyenExpressCheckoutPaymentTypes as $adyenPaymentMethodType} +
+ + + +
+
+
+ {/foreach} +
+
+
+ {/block} + {/if} +{/block} diff --git a/Resources/views/frontend/checkout/change_payment.tpl b/Resources/views/frontend/checkout/change_payment.tpl index a33ed76c..721d3ab4 100644 --- a/Resources/views/frontend/checkout/change_payment.tpl +++ b/Resources/views/frontend/checkout/change_payment.tpl @@ -1,27 +1,25 @@ {extends file="parent:frontend/checkout/change_payment.tpl"} {block name='frontend_checkout_payment_content'} - {include file="frontend/checkout/adyen_libaries.tpl"} - {* Filter on storedPayments and default payment methods (SW 5 needs internally array for $sPayments) *} {assign var="paymentMethods" value=[]} {assign var="storedPaymentMethods" value=[]} {foreach $sPayments as $paymentMethod} - {if 'isStoredPayment'|array_key_exists:$paymentMethod && true === $paymentMethod.isStoredPayment} - {append var="paymentMethod" value=$paymentMethod.stored_method_umbrella_id index='id'} + {if $paymentMethod.isStoredPaymentMethod} + {append var="paymentMethod" value="{$paymentMethod.id}" index='originalId'} + {append var="paymentMethod" value="{$paymentMethod.id}_{$paymentMethod.storedPaymentMethodId}" index='id'} {$storedPaymentMethods[] = $paymentMethod} {else} + {append var="paymentMethod" value="{$paymentMethod.id}" index='originalId'} {$paymentMethods[] = $paymentMethod} {/if} {/foreach} - {include file="frontend/checkout/adyen_configuration.tpl"} - {block name='frontend_checkout_payment_content_adyen_stored_payment_methods'} {if !empty($storedPaymentMethods)}
{if !empty($paymentMethods)}

- {s namespace='adyen/checkout/payment' name='storedPaymentMethodTitle'}{/s} + {s namespace='frontend/adyen/checkout' name='payment/adyen/stored_payment_methods_title'}Stored payment methods{/s}

{/if} {assign var=sPayments value=$storedPaymentMethods} @@ -34,16 +32,21 @@ {if !empty($paymentMethods)} {if !empty($storedPaymentMethods)}

- {s namespace='adyen/checkout/payment' name='paymentMethodTitle'}{/s} + {s namespace='frontend/adyen/checkout' name='payment/adyen/payment_methods_title'}Payment methods{/s}

{/if} {assign var=sPayments value=$paymentMethods} {$smarty.block.parent} + + {/if} {/block} {/block} {block name='frontend_checkout_payment_fieldset_input_label'} + {if $payment_mean.surchargeAmount} + {append var="payment_mean" value="{$payment_mean.description} (+ {$payment_mean.surchargeAmount|currency})" index='description'} + {/if} {if $payment_mean.image}
{$payment_mean.description} @@ -57,6 +60,22 @@ {/if} {/block} +{block name='frontend_checkout_payment_fieldset_input_radio'} + {if $payment_mean.isAdyenPaymentMethod} +
+ +
+ {else} + {$smarty.block.parent} + {/if} +{/block} + {* Method Description *} {block name='frontend_checkout_payment_fieldset_description'} {if $payment_mean.name|strstr:"SwagPaymentPayPal"} diff --git a/Resources/views/frontend/checkout/confirm.tpl b/Resources/views/frontend/checkout/confirm.tpl index d331d95d..e2451d51 100644 --- a/Resources/views/frontend/checkout/confirm.tpl +++ b/Resources/views/frontend/checkout/confirm.tpl @@ -1,8 +1,9 @@ {extends file="parent:frontend/checkout/confirm.tpl"} -{block name='frontend_checkout_confirm_form'} - {include file="frontend/checkout/adyen_libaries.tpl"} - +{block name='frontend_checkout_confirm_error_messages'} +
+ {if $sErrorMessages}{include file="frontend/register/error_message.tpl" error_messages=$sErrorMessages}{/if} +
{$smarty.block.parent} {/block} @@ -16,42 +17,40 @@ {$smarty.block.parent} {/block} -{block name='frontend_index_body_attributes'} - {if $mAdyenSnippets}data-adyensnippets="{$mAdyenSnippets}"{/if} - {$adyenType=$sUserData.additional.payment['adyenType']|default:''} +{block name='frontend_checkout_confirm_information_wrapper'} {$smarty.block.parent} - data-adyenConfigAjaxUrl="{url module='frontend' controller='adyenconfig' action='index'}" - data-adyenAjaxDoPaymentUrl="{url module='frontend' controller='adyen' action='ajaxDoPayment'}" - data-adyenAjaxPaymentDetails="{url module='frontend' controller='adyen' action='paymentDetails'}" - data-checkoutShippingPaymentUrl="{url controller='checkout' action='shippingPayment' sTarget='checkout'}" - data-adyenAjaxThreeDsUrl="{url module='frontend' controller='adyen' action='ajaxThreeDs'}" - {if $mAdyenSnippets} - data-adyenSnippets="{$mAdyenSnippets}" - {/if} - {if $adyenType}data-adyenType="{$adyenType}"{/if} - {if $sAdyenGoogleConfig} - data-adyenGoogleConfig='{$sAdyenGoogleConfig}' - {/if} - {if $sAdyenApplePayConfig} - data-adyenApplePayConfig='{$sAdyenApplePayConfig}' - {/if} - {if $adyenPaymentState} - data-adyenPaymentState='{$adyenPaymentState}' - {/if} - {if $sUserData.additional.payment.source == $adyenSourceType} - data-adyenIsAdyenPayment='true' + + {if $sPayment.isAdyenPaymentMethod} + {/if} {/block} -{block name='frontend_checkout_confirm_payment_method_panel'} +{block name="frontend_index_after_body"} {$smarty.block.parent} - {include file="frontend/checkout/adyen_configuration.tpl"} -{/block} + {if $sPayment.isAdyenPaymentMethod} + {include file="frontend/checkout/adyen_libaries.tpl"} -{block name='frontend_checkout_confirm_error_messages'} -
- {if $sErrorMessages}{include file="frontend/register/error_message.tpl" error_messages=$sErrorMessages}{/if} -
+
+
+ {/if} + +{/block} + +{block name="frontend_index_after_body"} {$smarty.block.parent} + + {if $sPayment.adyenPaymentType == 'googlepay' || $sPayment.adyenPaymentType == 'paywithgoogle'} + + {/if} + {/block} diff --git a/Resources/views/frontend/checkout/finish.tpl b/Resources/views/frontend/checkout/finish.tpl new file mode 100644 index 00000000..24d2282a --- /dev/null +++ b/Resources/views/frontend/checkout/finish.tpl @@ -0,0 +1,50 @@ +{extends file="parent:frontend/checkout/finish.tpl"} + +{block name="frontend_index_after_body"} + {$smarty.block.parent} + + {include file="frontend/checkout/adyen_libaries.tpl"} +{/block} + +{block name='frontend_checkout_finish_teaser_actions'} +
+ + {if $sErrorMessages} +
+ {include file="frontend/register/error_message.tpl" error_messages=$sErrorMessages} +
+ {/if} + {if $sSuccessMessages} +
+ {include file="frontend/_includes/messages.tpl" type="success" content=$sSuccessMessages} +
+ {/if} + + {$smarty.block.parent} +{/block} + +{block name='frontend_checkout_finish_teaser_actions'} +
+{/block} diff --git a/Resources/views/frontend/checkout/shipping_payment.tpl b/Resources/views/frontend/checkout/shipping_payment.tpl index 078393a7..6cbf7bae 100644 --- a/Resources/views/frontend/checkout/shipping_payment.tpl +++ b/Resources/views/frontend/checkout/shipping_payment.tpl @@ -1,6 +1,20 @@ {extends file="parent:frontend/checkout/shipping_payment.tpl"} +{block name="frontend_index_after_body"} + {$smarty.block.parent} + + {include file="frontend/checkout/adyen_libaries.tpl"} +{/block} + {block name="frontend_index_content"} + {s namespace="frontend/adyen/checkout" name="payment/adyen/update_payment_info_button_text" assign="snippetUpdatePaymentInfoButtonText"}{/s} +
+
+ +
{$smarty.block.parent}
diff --git a/Resources/views/frontend/detail/buy.tpl b/Resources/views/frontend/detail/buy.tpl new file mode 100755 index 00000000..d502f486 --- /dev/null +++ b/Resources/views/frontend/detail/buy.tpl @@ -0,0 +1,31 @@ +{extends file='parent:frontend/detail/buy.tpl'} + +{* Express Checkout buttons *} +{block name='frontend_detail_buy'} + {$smarty.block.parent} + + {if $adyenShowExpressCheckout && (!$sArticle.sConfigurator || ($sArticle.sConfigurator && $activeConfiguratorSelection)) } + {block name='adyen_frontend_detail_express_buy_button'} + {assign var="adyenExpressCheckoutPaymentTypes" value=['applepay', 'amazonpay', 'paywithgoogle', 'paypal']} + {foreach $adyenExpressCheckoutPaymentTypes as $adyenPpaymentMethodType} +
+ + + + +
+
+
+ {/foreach} + {/block} + {/if} +{/block} diff --git a/Resources/views/frontend/detail/index.tpl b/Resources/views/frontend/detail/index.tpl new file mode 100644 index 00000000..782c4291 --- /dev/null +++ b/Resources/views/frontend/detail/index.tpl @@ -0,0 +1,11 @@ +{extends file='parent:frontend/detail/index.tpl'} + +{block name="frontend_index_after_body"} + {$smarty.block.parent} + + {if $adyenShowExpressCheckout } + {include file="frontend/checkout/adyen_libaries.tpl"} + + {/if} + +{/block} diff --git a/Resources/views/frontend/register/payment_fieldset.tpl b/Resources/views/frontend/register/payment_fieldset.tpl index 412bc07a..2012f58b 100644 --- a/Resources/views/frontend/register/payment_fieldset.tpl +++ b/Resources/views/frontend/register/payment_fieldset.tpl @@ -1,9 +1,8 @@ {extends file='parent:frontend/register/payment_fieldset.tpl'} {block name="frontend_register_payment_fieldset_input_radio"} - {assign var='isStoredPayment' value=('isStoredPayment'|array_key_exists:$payment_mean && true === $payment_mean.isStoredPayment)} - {if $isStoredPayment} - {append var="payment_mean" value=($payment_mean.stored_method_umbrella_id) index='id'} + {if $payment_mean.isStoredPaymentMethod} + {append var="payment_mean" value="{$payment_mean.id}_{$payment_mean.storedPaymentMethodId}" index='id'} {/if} {/block} @@ -21,14 +20,14 @@ {$smarty.block.parent} {block name="frontend_register_payment_stored_method_action_disable"} - {if $isStoredPayment } + {if $payment_mean.isStoredPaymentMethod }
{/if} @@ -39,13 +38,13 @@ {$smarty.block.parent}
-

{s name='adyenDisableTokenConfirmationMessage'}Are you sure to remove the stored payment method?{/s}

+

{s name='payment/adyen/disable_confirm_message'}Are you sure to remove the stored payment method?{/s}

diff --git a/Rule/AdyenApi/IsMainShopApiKeyRule.php b/Rule/AdyenApi/IsMainShopApiKeyRule.php deleted file mode 100755 index debc9f95..00000000 --- a/Rule/AdyenApi/IsMainShopApiKeyRule.php +++ /dev/null @@ -1,25 +0,0 @@ -configuration = $configuration; - } - - public function __invoke(Shop $shop, Shop $mainShop): bool - { - return $this->configuration->getApiKey($mainShop) - === $this->configuration->getApiKey($shop); - } -} diff --git a/Rule/AdyenApi/IsMainShopMerchantAccountRule.php b/Rule/AdyenApi/IsMainShopMerchantAccountRule.php deleted file mode 100755 index db213242..00000000 --- a/Rule/AdyenApi/IsMainShopMerchantAccountRule.php +++ /dev/null @@ -1,25 +0,0 @@ -configuration = $configuration; - } - - public function __invoke(Shop $shop, Shop $mainShop): bool - { - return $this->configuration->getMerchantAccount($mainShop) - === $this->configuration->getMerchantAccount($shop); - } -} diff --git a/Rule/AdyenApi/MainShopConfigRule.php b/Rule/AdyenApi/MainShopConfigRule.php deleted file mode 100644 index 43ec4c4c..00000000 --- a/Rule/AdyenApi/MainShopConfigRule.php +++ /dev/null @@ -1,12 +0,0 @@ -mainShopConfigRules = $mainShopConfigRules; - } - - public function __invoke(Shop $shop, Shop $mainShop): bool - { - foreach ($this->mainShopConfigRules as $rule) { - if ($rule($shop, $mainShop)) { - return true; - } - } - - return false; - } -} diff --git a/Rule/AdyenApi/MainShopRule.php b/Rule/AdyenApi/MainShopRule.php deleted file mode 100644 index 7493dda8..00000000 --- a/Rule/AdyenApi/MainShopRule.php +++ /dev/null @@ -1,10 +0,0 @@ -shopRepository = $shopRepository; - $this->mainShopConfigRuleChain = $mainShopConfigRule; - } - - public function __invoke(int $shopId): bool - { - if (1 === $shopId) { - return false; - } - - return ($this->mainShopConfigRuleChain)( - $this->shopRepository->find($shopId), - $this->shopRepository->find(1) - ); - } -} diff --git a/Rule/AdyenApi/UsedFallbackConfigRuleInterface.php b/Rule/AdyenApi/UsedFallbackConfigRuleInterface.php deleted file mode 100644 index d9810441..00000000 --- a/Rule/AdyenApi/UsedFallbackConfigRuleInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -usedFallbackConfigRule = $usedFallbackConfigRule; - } - - public function __invoke(int $shopId): bool - { - if (1 === $shopId) { - return true; - } - - return ($this->usedFallbackConfigRule)($shopId); - } -} diff --git a/Serializer/PaymentMeanCollectionSerializer.php b/Serializer/PaymentMeanCollectionSerializer.php deleted file mode 100644 index 11f971d4..00000000 --- a/Serializer/PaymentMeanCollectionSerializer.php +++ /dev/null @@ -1,15 +0,0 @@ -> - */ - public function __invoke(PaymentMeanCollection $paymentMeans): array; -} diff --git a/Serializer/PaymentMeanSerializer.php b/Serializer/PaymentMeanSerializer.php deleted file mode 100644 index ce28859b..00000000 --- a/Serializer/PaymentMeanSerializer.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ - public function __invoke(PaymentMean $paymentMean): array; -} diff --git a/Session/CustomerNumberProvider.php b/Session/CustomerNumberProvider.php deleted file mode 100755 index 08a77639..00000000 --- a/Session/CustomerNumberProvider.php +++ /dev/null @@ -1,35 +0,0 @@ -session = $session; - $this->modelManager = $modelManager; - } - - public function __invoke(): string - { - $userId = $this->session->get('sUserId'); - if (!$userId) { - return ''; - } - $customer = $this->modelManager->getRepository(Customer::class)->find($userId); - - return $customer ? (string) $customer->getNumber() : ''; - } -} diff --git a/Session/CustomerNumberProviderInterface.php b/Session/CustomerNumberProviderInterface.php deleted file mode 100644 index d47396a6..00000000 --- a/Session/CustomerNumberProviderInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -session = $session; - } - - public function hasMessages(): bool - { - return (bool)$this->session->get(self::KEY_ERROR_MESSAGES); - } - - public function add(string ...$messages): void - { - $this->session->offsetSet( - self::KEY_ERROR_MESSAGES, - array_merge( - array_values($this->read()), - array_values($messages) - ) - ); - } - - public function read(): array - { - $messages = (array) ($this->session->offsetGet(self::KEY_ERROR_MESSAGES) ?? []); - $this->session->offsetUnset(self::KEY_ERROR_MESSAGES); - - return $messages; - } -} diff --git a/Setup/MigrateTransactionHistoryTask.php b/Setup/MigrateTransactionHistoryTask.php new file mode 100644 index 00000000..125d1ead --- /dev/null +++ b/Setup/MigrateTransactionHistoryTask.php @@ -0,0 +1,614 @@ +toArray()); + } + + /** + * @inheritDoc + */ + public function unserialize($serialized) + { + $unserialized = Serializer::unserialize($serialized); + + $this->handledOrders = $unserialized['handledOrders']; + $this->textNotificationsOffset = $unserialized['textNotificationsOffset']; + } + + /** + * @inheritDoc + */ + public static function fromArray(array $array) + { + $task = new self(); + $task->handledOrders = $array['handledOrders']; + $task->textNotificationsOffset = $array['textNotificationsOffset']; + + return $task; + } + + /** + * @inheritDoc + */ + public function toArray() + { + return [ + 'handledOrders' => $this->handledOrders, + 'textNotificationsOffset' => $this->textNotificationsOffset + ]; + } + + public function execute(): void + { + $orderIds = $this->getOrderIdsForMigration(); + while (!empty($orderIds)) { + $this->processOrderIdsBatch($orderIds); + + $this->handledOrders += self::BATCH_SIZE; + $orderIds = $this->getOrderIdsForMigration(); + } + + $this->reportProgress(70); + + $textNotifications = $this->getTextNotifications(); + while (!empty($textNotifications)) { + $this->processTextNotificationsBatch($textNotifications); + + $this->textNotificationsOffset += self::BATCH_SIZE; + $textNotifications = $this->getTextNotifications(); + } + + $this->dropLegacyTables(); + $this->reportProgress(100); + } + + private function processOrderIdsBatch(array $orderIds): void + { + foreach ($orderIds as $orderId) { + $handledNotifications = $this->getHandledNotificationsForOrderId($orderId); + if (!empty($handledNotifications)) { + $this->processHandledNotificationsBatch($handledNotifications); + } + + $this->reportAlive(); + + $unhandledNotifications = $this->getUnhandledNotificationsForOrderId($orderId); + if (!empty($unhandledNotifications)) { + $this->processUnhandledNotificationsBatch($unhandledNotifications); + } + } + } + + private function dropLegacyTables(): void + { + $this->getConnection()->executeQuery( + 'DROP TABLE IF EXISTS `s_plugin_adyen_order_notification`; + DROP TABLE IF EXISTS `s_plugin_adyen_text_notification`;' + ); + } + + private function processHandledNotificationsBatch(array $notifications): void + { + $ordersMap = $this->getOrderMapFor($notifications); + /** @var TransactionHistory[] $transactionHistoryMap */ + $transactionHistoryMap = $this->getTransactionHistoryMapFor($notifications, $ordersMap); + + foreach ($notifications as $notification) { + if ( + !array_key_exists($notification['order_id'], $ordersMap) || + !array_key_exists($notification['order_id'], $transactionHistoryMap) + ) { + continue; + } + + $order = $ordersMap[$notification['order_id']]; + $transactionHistory = $transactionHistoryMap[$notification['order_id']]; + + StoreContext::doWithStore( + $order->getShop()->getId(), + function () use ($notification, $order, $transactionHistory) { + $this + ->getTransactionLogRepository() + ->setTransactionLog($this->transformNotificationToLog($notification, $order)); + + $this->updateTransactionHistoryWith($transactionHistory, $notification, $order); + } + ); + } + } + + private function processUnhandledNotificationsBatch(array $notifications): void + { + $ordersMap = $this->getOrderMapFor($notifications); + /** @var TransactionHistory[] $transactionHistoryMap */ + $transactionHistoryMap = $this->getTransactionHistoryMapFor($notifications, $ordersMap); + + foreach ($notifications as $notification) { + if ( + !array_key_exists($notification['order_id'], $ordersMap) || + !array_key_exists($notification['order_id'], $transactionHistoryMap) + ) { + continue; + } + + $order = $ordersMap[$notification['order_id']]; + $transactionHistory = $transactionHistoryMap[$notification['order_id']]; + + StoreContext::doWithStore( + $order->getShop()->getId(), + function () use ($notification, $order, $transactionHistory) { + $this->getQueueService()->enqueue( + 'OrderUpdate', + new OrderUpdateTask( + $this->transformNotificationToWebhook($notification, $order, $transactionHistory) + ) + ); + } + ); + } + } + + private function processTextNotificationsBatch(array $textNotifications): void + { + $webhooks = array_map(function (array $textNotification) { + return $this->transformTextNotificationToWebhook( + (array)json_decode($textNotification['text_notification'], true) + ); + }, $textNotifications); + + + $ordersMap = $this->getOrderMapForWebhooks($webhooks); + foreach ($webhooks as $webhook) { + if (!array_key_exists($webhook->getMerchantReference(), $ordersMap)) { + continue; + } + + $order = $ordersMap[$webhook->getMerchantReference()]; + // Set webhook merchant reference to temporary id for new processing logic to work as expected + $webhookForProcessing = $this->cloneWebhookWithNewMerchantReference($webhook, $order->getTemporaryId()); + + StoreContext::doWithStore( + $order->getShop()->getId(), + function () use ($webhookForProcessing) { + $this->getWebhookHandler()->handle($webhookForProcessing); + } + ); + } + } + + /** + * @return string[] + */ + private function getOrderIdsForMigration(): array + { + $dateLimit = (new DateTime())->sub(new DateInterval('P30D')); + $query = $this->getConnection()->createQueryBuilder() + ->select('DISTINCT order_id as order_id') + ->from('s_plugin_adyen_order_notification', 'notification') + ->where('notification.created_at >= :createdAt') + ->setParameter('createdAt', $dateLimit, 'datetime') + ->setMaxResults(self::BATCH_SIZE) + ->setFirstResult($this->handledOrders); + + return array_map(static function ($notification) { + return (string)$notification['order_id']; + }, $query->execute()->fetchAll(PDO::FETCH_ASSOC)); + } + + /** + * @param string $orderId + * @return array + */ + private function getHandledNotificationsForOrderId(string $orderId): array + { + $query = $this->getConnection()->createQueryBuilder() + ->select('*') + ->from('s_plugin_adyen_order_notification', 'notification') + ->where('notification.order_id = :orderId') + ->andWhere('notification.status IN(:status)') + ->setParameter('orderId', $orderId) + ->setParameter('status', ['handled', 'error', 'fatal'], Connection::PARAM_STR_ARRAY) + ->orderBy('id'); + + return $query->execute()->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * @param string $orderId + * @return array + */ + private function getUnhandledNotificationsForOrderId(string $orderId): array + { + $query = $this->getConnection()->createQueryBuilder() + ->select('*') + ->from('s_plugin_adyen_order_notification', 'notification') + ->where('notification.order_id = :orderId') + ->andWhere('notification.status NOT IN(:status)') + ->setParameter('orderId', $orderId) + ->setParameter('status', ['handled', 'error', 'fatal'], Connection::PARAM_STR_ARRAY) + ->orderBy('id'); + + return $query->execute()->fetchAll(PDO::FETCH_ASSOC); + } + + private function getTextNotifications(): array + { + $query = $this->getConnection()->createQueryBuilder() + ->select('*') + ->from('s_plugin_adyen_text_notification', 'notification') + ->orderBy('id') + ->setMaxResults(self::BATCH_SIZE) + ->setFirstResult($this->textNotificationsOffset); + + return $query->execute()->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Transforms notification in the context of the provided Shopware shop + * + * @param array $notification + * @param Order $order + * @return TransactionLog + */ + private function transformNotificationToLog(array $notification, Order $order): TransactionLog + { + $transactionLog = new TransactionLog(); + $transactionLog->setStoreId((string)$order->getShop()->getId()); + $transactionLog->setMerchantReference((string)$order->getTemporaryId()); + $transactionLog->setExecutionId(0); + $transactionLog->setEventCode((string)$notification['event_code']); + $transactionLog->setReason((string)$notification['error_details']); + $transactionLog->setIsSuccessful((bool)$notification['success']); + $transactionLog->setTimestamp( + DateTime::createFromFormat('Y-m-d H:i:s', $notification['created_at'])->getTimestamp() + ); + $transactionLog->setPaymentMethod((string)$notification['paymentMethod']); + $transactionLog->setAdyenLink($this->getAdyenLink($notification['psp_reference'], $order->getShop())); + $transactionLog->setShopLink( + $this->getOrderService()->getOrderUrlForId((int)$notification['order_id']) + ); + $transactionLog->setQueueStatus( + $notification['status'] === 'handled' ? QueueItem::COMPLETED : QueueItem::FAILED + ); + $transactionLog->setPspReference($notification['psp_reference']); + + return $transactionLog; + } + + /** + * @throws InvalidMerchantReferenceException + */ + private function updateTransactionHistoryWith( + TransactionHistory $transactionHistory, + array $notification, + Order $order + ): void { + $webhook = $this->transformNotificationToWebhook($notification, $order, $transactionHistory); + + $transactionHistory->add( + new HistoryItem( + $webhook->getPspReference(), + $webhook->getMerchantReference(), + $webhook->getEventCode(), + $this->getOrderStatusProvider()->getNewPaymentState($webhook, $transactionHistory), + $webhook->getEventDate(), + $webhook->isSuccess(), + $webhook->getAmount(), + $webhook->getPaymentMethod(), + $webhook->getRiskScore(), + $webhook->isLive() + ) + ); + + $this->getTransactionHistoryService()->setTransactionHistory($transactionHistory); + } + + private function transformNotificationToWebhook( + array $notification, + Order $order, + TransactionHistory $transactionHistory + ): Webhook { + $config = $this->getCachedConfigReader()->getByPluginName(AdyenPayment::NAME, $order->getShop()); + return new Webhook( + Amount::fromInt( + (int)$notification['amount_value'], + !empty($notification['amount_currency']) ? Currency::fromIsoCode( + $notification['amount_currency'] + ) : Currency::getDefault() + ), + (string)$notification['event_code'], + DateTime::createFromFormat('Y-m-d H:i:s', $notification['created_at'])->format(DateTimeInterface::ATOM), + '', + '', + (string)$order->getTemporaryId(), + (string)$notification['psp_reference'], + (string)$notification['paymentMethod'], + (string)$notification['error_details'], + (bool)$notification['success'], + $transactionHistory->getOriginalPspReference(), + 0, + !empty($config['environment']) && 'LIVE' === mb_strtoupper($config['environment']) + ); + } + + private function transformTextNotificationToWebhook(array $textNotification): Webhook + { + return new Webhook( + Amount::fromInt( + $textNotification['amount']['value'] ?? 0, + $textNotification['amount']['currency'] ? Currency::fromIsoCode( + $textNotification['amount']['currency'] + ) : Currency::getDefault() + ), + $textNotification['eventCode'] ?? '', + $textNotification['eventDate'] ?? '', + $textNotification['additionalData']['hmacSignature'] ?? '', + $textNotification['merchantAccountCode'] ?? '', + $textNotification['merchantReference'] ?? '', + $textNotification['pspReference'] ?? '', + $textNotification['paymentMethod'] ?? '', + $textNotification['reason'] ?? '', + $textNotification['success'] === 'true', + $textNotification['originalReference'] ?? '', + $textNotification['additionalData']['totalFraudScore'] ?? 0, + $textNotification['live'] === 'true' + ); + } + + /** + * Gets the same webhook instance as provided one but with changed merchant reference from parameter + * + * @param Webhook $webhook + * @param string $merchantReference + * @return Webhook + */ + private function cloneWebhookWithNewMerchantReference(Webhook $webhook, string $merchantReference): Webhook + { + return new Webhook( + $webhook->getAmount(), + $webhook->getEventCode(), + $webhook->getEventDate(), + $webhook->getHmacSignature(), + $webhook->getMerchantAccountCode(), + $merchantReference, + $webhook->getPspReference(), + $webhook->getPaymentMethod(), + $webhook->getReason(), + $webhook->isSuccess(), + $webhook->getOriginalReference(), + $webhook->getRiskScore(), + $webhook->isLive() + ); + } + + private function getAdyenLink(string $pspReference, Shop $shop): string + { + $domain = 'ca-test.adyen.com'; + + $config = $this->getCachedConfigReader()->getByPluginName(AdyenPayment::NAME, $shop); + if (!empty($config['environment']) && 'LIVE' === mb_strtoupper($config['environment'])) { + $domain = 'ca-live.adyen.com'; + } + + return "https://$domain/ca/ca/config/event-logs.shtml?query=$pspReference"; + } + + /** + * @param array $notifications + * @return Order[] + */ + private function getOrderMapFor(array $notifications): array + { + return $this->getOrderRepository()->getOrdersByIds( + array_map(static function (array $notification) { + return $notification['order_id']; + }, $notifications) + ); + } + + /** + * @param Webhook[] $webhooks + * @return Order[] + */ + private function getOrderMapForWebhooks(array $webhooks): array + { + return $this->getOrderRepository()->getOrdersByNumbers( + array_map(static function (Webhook $webhook) { + return $webhook->getMerchantReference(); + }, $webhooks) + ); + } + + /** + * @param array $notifications + * @param Order[] $ordersMap + * + * @return array + * + * @throws InvalidMerchantReferenceException + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + * @throws InvalidCurrencyCode + */ + private function getTransactionHistoryMapFor(array $notifications, array $ordersMap): array + { + $ordersByTempIdMap = []; + foreach ($ordersMap as $order) { + $ordersByTempIdMap[$order->getTemporaryId()] = $order; + } + + $queryFilter = new QueryFilter(); + $queryFilter + ->where( + 'merchantReference', + Operators::IN, + array_map( + static function (array $notification) use ($ordersMap) { + $merchantReference = ''; + if (array_key_exists($notification['order_id'], $ordersMap)) { + $merchantReference = $ordersMap[$notification['order_id']]->getTemporaryId(); + } + + return $merchantReference; + }, + $notifications + ) + ); + + /** @var TransactionEntity[] $entities */ + $entities = RepositoryRegistry::getRepository(TransactionEntity::class)->select($queryFilter); + $transactionHistoryMap = []; + foreach ($entities as $transactionHistoryEntity) { + $transactionHistory = $transactionHistoryEntity->getTransactionHistory(); + $order = $ordersByTempIdMap[$transactionHistory->getMerchantReference()]; + $transactionHistoryMap[$order->getId()] = $transactionHistory; + } + + // Ensure that each notification has its transaction history initialized if DB does not have it until now + foreach ($notifications as $notification) { + if (!array_key_exists($notification['order_id'], $ordersMap)) { + continue; + } + + $merchantReference = $ordersMap[$notification['order_id']]->getTemporaryId(); + if (!array_key_exists($notification['order_id'], $transactionHistoryMap)) { + $transactionHistoryMap[$notification['order_id']] = new TransactionHistory( + $merchantReference, + CaptureType::unknown(), + 0, + Currency::fromIsoCode($ordersMap[$notification['order_id']]->getCurrency()) + ); + } + } + + return $transactionHistoryMap; + } + + /** + * @return Connection + */ + private function getConnection(): Connection + { + return Shopware()->Container()->get('dbal_connection'); + } + + /** + * @return CachedConfigReader + */ + private function getCachedConfigReader(): CachedConfigReader + { + return Shopware()->Container()->get('shopware.plugin.cached_config_reader'); + } + + /** + * @return OrderRepository + */ + private function getOrderRepository(): OrderRepository + { + return Shopware()->Container()->get(OrderRepository::class); + } + + /** + * @return OrderService + */ + private function getOrderService(): OrderService + { + return Shopware()->Container()->get(OrderService::class); + } + + /** + * @return TransactionLogRepository + */ + private function getTransactionLogRepository(): TransactionLogRepository + { + return ServiceRegister::getService(TransactionLogRepository::class); + } + + /** + * @return TransactionHistoryService + */ + private function getTransactionHistoryService(): TransactionHistoryService + { + return ServiceRegister::getService(TransactionHistoryService::class); + } + + /** + * @return OrderStatusProvider + */ + private function getOrderStatusProvider(): OrderStatusProvider + { + return ServiceRegister::getService(OrderStatusProvider::class); + } + + /** + * @return QueueService + */ + private function getQueueService(): QueueService + { + return ServiceRegister::getService(QueueService::class); + } + + /** + * @return WebhookHandler + */ + private function getWebhookHandler(): WebhookHandler + { + return ServiceRegister::getService(WebhookHandler::class); + } +} diff --git a/Setup/Updater.php b/Setup/Updater.php new file mode 100644 index 00000000..ec08a174 --- /dev/null +++ b/Setup/Updater.php @@ -0,0 +1,635 @@ +context = $context; + $this->configReader = $configReader; + $this->connectionService = $connectionService; + $this->storeRepository = $storeRepository; + $this->paymentMethodConfigRepository = $paymentMethodConfigRepository; + $this->snippets = $snippets; + $this->cronManager = $cronManager; + $this->queueService = $queueService; + $this->connectionSettingsRepository = $connectionSettingsRepository; + } + + public function update(): void + { + $oldVersion = $this->context->getCurrentVersion(); + if (version_compare($oldVersion, '4.0.0', '<')) { + $this->updateTo400(); + } + } + + private function updateTo400(): void + { + $shops = $this->storeRepository->getShopwareSubShops(); + $shopsWithValidConnection = $this->migrateConnectionSettingsTo400($shops); + $this->migratePaymentMeansTo400($shopsWithValidConnection); + $this->migratePaymentMethodConfigsTo400($shopsWithValidConnection); + $this->migrateCronsTo400(); + $this->initializeTransactionDetailsMigrationTo400(); + $this->deleteObsoleteConfiguration(); + } + + /** + * Migrates connection configuration for list of shops provided. + * + * @param Shop[] $shops + * @return Shop[] Shops where connection settings are migrated + * @throws Exception + */ + private function migrateConnectionSettingsTo400(array $shops): array + { + $migratedShops = []; + foreach ($shops as $shop) { + $config = $this->configReader->getByPluginName(AdyenPayment::NAME, $shop); + if (empty($config)) { + continue; + } + + StoreContext::doWithStore( + $shop->getId(), + function () use ($shop, $config, &$migratedShops) { + try { + if ($this->migrateConnectionSettingsForShopTo400($shop, $config)) { + $migratedShops[] = $shop; + } + } catch (Exception $e) { + Logger::logWarning('Failed to migrate connection for ' . $shop->getName()); + } + } + ); + } + + return $migratedShops; + } + + /** + * @param Shop[] $shopsWithValidConnection + * @return void + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + private function migratePaymentMeansTo400(array $shopsWithValidConnection): void + { + $paymentMeans = $this->getAllAdyenPaymentMeans(); + + if (empty($shopsWithValidConnection)) { + foreach ($paymentMeans as $paymentMean) { + // Skip already imported payments + if (Plugin::isAdyenPaymentMean($paymentMean->getName())) { + continue; + } + $paymentMean->setActive(false); + Shopware()->Models()->persist($paymentMean); + } + + Shopware()->Models()->flush(); + + return; + } + + $languageStores = $this->storeRepository->getShopwareLanguageShops(array_map(static function (Shop $shop) { + return $shop->getId(); + }, $shopsWithValidConnection)); + $paymentMeanShops = new ArrayCollection(array_merge($shopsWithValidConnection, $languageStores)); + + foreach ($paymentMeans as $paymentMean) { + // Skip already imported payments + if (Plugin::isAdyenPaymentMean($paymentMean->getName())) { + continue; + } + + $enabledShops = []; + $shops = $paymentMean->getShops(); + + foreach ($shops->toArray() as $shop) { + foreach ($paymentMeanShops->toArray() as $paymentMeanShop) { + if ($shop->getId() === $paymentMeanShop->getId()) { + $enabledShops[] = $shop; + continue 2; + } + } + } + + $code = $this->getAdyenPaymentMethodCode($paymentMean); + if (!empty($code)) { + $paymentMean + ->setName(Plugin::getPaymentMeanName($code)) + ->setAction('AdyenPaymentProcess') + ->setShops(new ArrayCollection($enabledShops)); + } else { + $paymentMean->setActive(false); + } + + Shopware()->Models()->persist($paymentMean); + } + + Shopware()->Models()->flush(); + } + + /** + * @param Shop[] $shopsWithValidConnection + * @return void + * @throws Exception + */ + private function migratePaymentMethodConfigsTo400(array $shopsWithValidConnection): void + { + $paymentMeans = $this->getAllAdyenPaymentMeans(true); + if (empty($paymentMeans)) { + return; + } + + $oneyInstallmentsMap = [ + (string)PaymentMethodCode::facilyPay3x() => '3', + (string)PaymentMethodCode::facilyPay4x() => '4', + (string)PaymentMethodCode::facilyPay6x() => '6', + (string)PaymentMethodCode::facilyPay10x() => '10', + (string)PaymentMethodCode::facilyPay12x() => '12', + ]; + $oneyActiveInstallments = []; + $paymentMeansMap = []; + foreach ($paymentMeans as $paymentMean) { + $adyenPaymentCode = Plugin::getAdyenPaymentType($paymentMean->getName()); + $paymentMeansMap[$adyenPaymentCode] = $paymentMean; + if (PaymentMethodCode::isOneyMethod($adyenPaymentCode)) { + $paymentMeansMap[(string)PaymentMethodCode::oney()] = $paymentMean; + $oneyActiveInstallments[] = $oneyInstallmentsMap[$adyenPaymentCode]; + } + } + + foreach ($shopsWithValidConnection as $shop) { + $adyenPaymentMethods = AdminAPI::get()->payment($shop->getId())->getAvailablePaymentMethods(); + $config = $this->configReader->getByPluginName(AdyenPayment::NAME, $shop); + + $configMerchantId = array_key_exists('merchant_account', $config) ? (string)$config['merchant_account'] : ''; + $googleMerchantId = array_key_exists('google_merchant_id', $config) ? (string)$config['google_merchant_id'] : ''; + $oneyIsConfigured = false; + + foreach ($adyenPaymentMethods->toArray() as $availablePaymentMethod) { + if (!array_key_exists($availablePaymentMethod['code'], $paymentMeansMap)) { + continue; + } + + if ($oneyIsConfigured && PaymentMethodCode::isOneyMethod($availablePaymentMethod['code'])) { + continue; + } + + $shouldMigrate = false; + foreach ($paymentMeansMap[$availablePaymentMethod['code']]->getShops() as $paymentMeanShop) { + if ($paymentMeanShop->getId() === $shop->getId()) { + $shouldMigrate = true; + break; + } + } + + if (!$shouldMigrate) { + continue; + } + + $paymentMethod = new PaymentMethod( + $availablePaymentMethod['methodId'], + $availablePaymentMethod['code'], + $availablePaymentMethod['name'], + $availablePaymentMethod['logo'], + $availablePaymentMethod['status'], + $availablePaymentMethod['currencies'], + $availablePaymentMethod['countries'], + $availablePaymentMethod['paymentType'], + 'Adyen ' . $availablePaymentMethod['name'], + 'none' + ); + + if (PaymentMethodCode::isOneyMethod($availablePaymentMethod['code'])) { + $oneyIsConfigured = true; + $paymentMethod->setAdditionalData(new Oney($oneyActiveInstallments)); + } + + if ( + PaymentMethodCode::googlePay()->equals($availablePaymentMethod['code']) || + PaymentMethodCode::payWithGoogle()->equals($availablePaymentMethod['code']) + ) { + $paymentMethod->setAdditionalData(new GooglePay($configMerchantId, $googleMerchantId)); + } + + StoreContext::doWithStore( + $shop->getId(), + function () use ($paymentMethod) { + $this->paymentMethodConfigRepository->saveMethodConfiguration($paymentMethod); + } + ); + } + } + } + + private function migrateCronsTo400(): void + { + $obsoleteCrons = [ + 'Shopware_CronJob_AdyenPaymentProcessNotifications', + 'AdyenPayment_CronJob_ImportPaymentMethods' + ]; + + foreach ($obsoleteCrons as $obsoleteCron) { + $cronJob = $this->cronManager->getJobByAction($obsoleteCron); + if ($cronJob) { + $this->cronManager->deleteJob($cronJob); + } + } + } + + private function initializeTransactionDetailsMigrationTo400() + { + $this->queueService->enqueue('general_migration', new MigrateTransactionHistoryTask()); + + // Shopware can show only one message as a result of the update, If message is already set, do not override it. + $scheduled = $this->context->getScheduled(); + if (!empty($scheduled['message'])) { + return; + } + + /** @noinspection PhpParamsInspection */ + $this->context->scheduleMessage([ + 'title' => $this->snippets + ->getNamespace('backend/adyen/configuration') + ->get('payment/adyen/update/transaction_history_info_title', 'Migration started', true), + 'text' => $this->snippets + ->getNamespace('backend/adyen/configuration') + ->get( + 'payment/adyen/update/transaction_history_info_description', + 'The migration of existing Adyen transactions has started in the background', + true + ), + ]); + } + + /** + * @return void + * + * @throws \Doctrine\DBAL\Exception + */ + private function deleteObsoleteConfiguration() + { + $pluginId = $this->context->getPlugin()->getId(); + /** @var Connection $connection */ + $connection = Shopware()->Container()->get('dbal_connection'); + + /** @noinspection SqlDialectInspection */ + $sql = <<executeUpdate($sql, [':pluginId' => $pluginId]); + } + + private function migrateConnectionSettingsForShopTo400(Shop $shop, $config): bool + { + $configMode = array_key_exists('environment', $config) ? $config['environment'] : ''; + $configMerchantId = array_key_exists('merchant_account', $config) ? (string)$config['merchant_account'] : ''; + $configTestApiKey = array_key_exists('api_key_test', $config) ? (string)$config['api_key_test'] : ''; + $configLiveApiKey = array_key_exists('api_key_live', $config) ? (string)$config['api_key_live'] : ''; + $configApiUrlPrefix = array_key_exists('api_url_prefix', $config) ? (string)$config['api_url_prefix'] : ''; + + if (empty($configMode) || empty($configMerchantId)) { + return false; + } + + if (empty($configTestApiKey) && empty($configLiveApiKey)) { + return false; + } + + $liveConnectionData = $this->getLiveConnectionData( + $shop, $configMerchantId, $configLiveApiKey, $configApiUrlPrefix + ); + $testConnectionData = $this->getTestConnectionData($shop, $configMerchantId, $configTestApiKey); + + if (!$liveConnectionData && !$testConnectionData) { + return false; + } + + $connectionSettings = new ConnectionSettings( + $shop->getId(), + 'LIVE' === mb_strtoupper($configMode) ? Mode::MODE_LIVE : Mode::MODE_TEST, + $testConnectionData, + $liveConnectionData + ); + + return $this->initializeConnection($shop, $connectionSettings); + } + + private function getLiveConnectionData( + Shop $shop, + string $configMerchantId, + ?string $configLiveApiKey = '', + ?string $configApiUrlPrefix = '' + ): ?ConnectionData + { + if (empty($configLiveApiKey) || empty($configApiUrlPrefix)) { + return null; + } + + $liveApiCredentials = $this->getApiCredentialsFor(new ConnectionSettings( + $shop->getId(), + Mode::MODE_LIVE, + null, + new ConnectionData($configLiveApiKey, $configMerchantId, $configApiUrlPrefix) + )); + + if (!$liveApiCredentials) { + return null; + } + + return new ConnectionData( + $configLiveApiKey, + $configMerchantId, + $configApiUrlPrefix, + '', + $liveApiCredentials + ); + } + + private function getTestConnectionData( + Shop $shop, + string $configMerchantId, + ?string $configTestApiKey = '' + ): ?ConnectionData + { + if (empty($configTestApiKey)) { + return null; + } + + $testApiCredentials = $this->getApiCredentialsFor(new ConnectionSettings( + $shop->getId(), + Mode::MODE_TEST, + new ConnectionData($configTestApiKey, $configMerchantId), + null + )); + + if (!$testApiCredentials) { + return null; + } + + return new ConnectionData( + $configTestApiKey, + $configMerchantId, + '', + '', + $testApiCredentials + ); + } + + private function getApiCredentialsFor(ConnectionSettings $connectionSettings): ?ApiCredentials + { + $apiCredentials = $this->getProxy($connectionSettings)->getApiCredentialDetails(); + + if (!$apiCredentials || !$apiCredentials->isActive()) { + return null; + } + + return $apiCredentials; + } + + private function initializeConnection(Shop $shop, ConnectionSettings $connectionSettings): bool + { + try { + $this->connectionSettingsRepository->setConnectionSettings($connectionSettings); + $this->connectionService->saveConnectionData($connectionSettings); + } catch (Exception $e) { + Logger::logWarning('Migration of connection settings failed for store ' . $shop->getId() + . ' because ' . $e->getMessage()); + + + if ($connectionSettings->getMode() === Mode::MODE_LIVE) { + $settings = new ConnectionSettings( + $connectionSettings->getStoreId(), + $connectionSettings->getMode(), + null, + new ConnectionData( + $connectionSettings->getLiveData()->getApiKey(), + '', + $connectionSettings->getLiveData()->getClientPrefix() + ) + ); + } else { + $settings = new ConnectionSettings( + $connectionSettings->getStoreId(), + $connectionSettings->getMode(), + new ConnectionData($connectionSettings->getTestData()->getApiKey(), ''), + null + ); + } + + $this->connectionSettingsRepository->setConnectionSettings($settings); + $this->showInvalidSettingsWarning($shop); + + return false; + } + + return true; + } + + /** + * @return Payment[] + */ + private function getAllAdyenPaymentMeans($onlyActive = false): array + { + $query = Shopware()->Models()->getRepository(Payment::class) + ->createQueryBuilder('paymentmeans') + ->where('paymentmeans.source = :source') + ->setParameter('source', AdyenPayment::PAYMENT_METHOD_SOURCE); + + if ($onlyActive) { + $query->andWhere('paymentmeans.active = 1'); + } + + return $query->getQuery()->getResult(); + } + + /** + * Gets Adyen payment method code based on previous payment mean generated in shop + * + * @param Payment $paymentMean + * @return string + */ + private function getAdyenPaymentMethodCode(Payment $paymentMean): string + { + $paymentMeanName = $paymentMean->getName(); + + // Adjustment for gift card brands + if (0 === stripos($paymentMeanName, 'giftcard_')) { + $paymentMeanName = str_replace(['giftcard_', '_'], '', $paymentMeanName); + } + + if ('eagleeyevoucher' === $paymentMeanName) { + $paymentMeanName = 'eagleeye_voucher'; + } + + // Sort methods so that longer codes are before shorter ones (handle cases like klarna and klarna_paynow) + $supportedPaymentMethods = PaymentMethodCode::SUPPORTED_PAYMENT_METHODS; + rsort($supportedPaymentMethods, SORT_STRING); + + foreach ($supportedPaymentMethods as $code) { + // Gift cards are matched by brand, skip umbrella payment method code + if (PaymentMethodCode::giftCard()->equals($code)) { + continue; + } + + // Old payment mean name is concatenation of adyen payment method code and underscored name + // (e.g. klarna_paynow_pay_now_with_klarna or scheme_credit_card) + if (0 === stripos($paymentMeanName, $code)) { + return $code; + } + } + + return ''; + } + + private function showInvalidSettingsWarning(Shop $shop): void + { + $moduleLink = sprintf( + 'javascript:sessionStorage.setItem("adl-active-store-id", "%s"); + Shopware.ModuleManager.createSimplifiedModule("AdyenPaymentMain#connection", { + title: "Adyen", + maximized: true + });', + $shop->getId() + ); + + /** @noinspection PhpParamsInspection */ + $this->context->scheduleMessage([ + 'title' => sprintf( + $this->snippets + ->getNamespace('backend/adyen/configuration') + ->get('payment/adyen/update/api_key_warning_title', 'Insufficient scope detected (%s)', true), + $shop->getName() + ), + 'text' => $this->snippets + ->getNamespace('backend/adyen/configuration') + ->get( + 'payment/adyen/update/api_key_warning_description', + 'Please reauthenticate with a new API key in order to continue using the Adyen plugin seamlessly', + true + ), + 'btnDetail' => [ + 'text' => $this->snippets + ->getNamespace('backend/adyen/configuration') + ->get( + 'payment/adyen/update/api_key_warning_open_button_text', + 'Open Adyen', + true + ), + 'link' => $moduleLink, + 'target' => '_self' + ] + ]); + } + + private function getProxy(ConnectionSettings $connectionSettings): ConnectionProxy + { + return ProxyFactory::makeProxy(ConnectionProxy::class, $connectionSettings); + } +} diff --git a/Shopware/Crud/AttributeWriter.php b/Shopware/Crud/AttributeWriter.php deleted file mode 100755 index de9da58c..00000000 --- a/Shopware/Crud/AttributeWriter.php +++ /dev/null @@ -1,51 +0,0 @@ -crudService = $crudService; - $this->entityManager = $entityManager; - } - - public function writeReadOnlyAttributes(string $attributeTable, array $columns, callable $writer): void - { - $this->updateReadonlyOnAttributes($attributeTable, $columns, false); - $writer(); - $this->updateReadonlyOnAttributes($attributeTable, $columns, true); - } - - private function updateReadonlyOnAttributes(string $attributeTable, array $columns, bool $readOnly): void - { - foreach ($columns as $column => $columnType) { - $this->crudService->update($attributeTable, $column, $columnType, ['readonly' => $readOnly]); - } - $this->rebuildAttributeModel($attributeTable); - } - - private function rebuildAttributeModel(string $attributeTable): void - { - $metaDataCache = $this->entityManager->getConfiguration()->getMetadataCacheImpl(); - if ($metaDataCache) { - $metaDataCache->deleteAll(); - } - - $this->entityManager->generateAttributeModels([$attributeTable]); - } -} diff --git a/Shopware/Crud/AttributeWriterInterface.php b/Shopware/Crud/AttributeWriterInterface.php deleted file mode 100644 index 0bea71e6..00000000 --- a/Shopware/Crud/AttributeWriterInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - $columns - */ - public function writeReadOnlyAttributes(string $attributeTable, array $columns, callable $writer): void; -} diff --git a/Shopware/Plugin/TraceablePluginIdProvider.php b/Shopware/Plugin/TraceablePluginIdProvider.php deleted file mode 100755 index 7f13ef0b..00000000 --- a/Shopware/Plugin/TraceablePluginIdProvider.php +++ /dev/null @@ -1,41 +0,0 @@ -pluginManager = $pluginManager; - $this->logger = $logger; - } - - /** - * @throws \Exception - */ - public function provideId(): int - { - try { - return $this->pluginManager->getPluginByName(AdyenPayment::NAME)->getId(); - } catch (\Exception $exception) { - $this->logger->critical( - 'Could not provide the "id" of plugin "'.AdyenPayment::NAME.'"', - ['exception' => $exception] - ); - - throw $exception; - } - } -} diff --git a/Shopware/Provider/CheckoutBasketProvider.php b/Shopware/Provider/CheckoutBasketProvider.php deleted file mode 100644 index 17618309..00000000 --- a/Shopware/Provider/CheckoutBasketProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -container = $container; - $this->view = new \Enlight_View_Default($engine); - $this->init(); - - parent::__construct(); - } - - public function __invoke($mergeProportional = true): array - { - // Initialize front controller to mitigate getBasket expectations - $this->Front(); - - return $this->getBasket($mergeProportional); - } -} diff --git a/Shopware/Provider/CheckoutBasketProviderInterface.php b/Shopware/Provider/CheckoutBasketProviderInterface.php deleted file mode 100644 index 3014f088..00000000 --- a/Shopware/Provider/CheckoutBasketProviderInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -Modules()->Admin()->sGetPaymentMeans(); - } -} diff --git a/Shopware/Provider/PaymentMeansProviderInterface.php b/Shopware/Provider/PaymentMeansProviderInterface.php deleted file mode 100644 index 67848b21..00000000 --- a/Shopware/Provider/PaymentMeansProviderInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -modelManager = $modelManager; - } - - public function existsByName(string $name): bool - { - return null !== $this->paymentRepository()->findOneBy(['name' => $name]); - } - - public function existsDuplicate(Payment $newPayment): bool - { - $payments = $this->paymentRepository()->findBy(['name' => $newPayment->getName()]) ?? []; - if (!count($payments)) { - return false; - } - - /** @psalm-var list $payments */ - foreach ($payments as $payment) { - if ($payment->getId() !== $newPayment->getId()) { - return true; - } - } - - return false; - } - - public function findByCode(string $code): ?Payment - { - $query = $this->paymentRepository() - ->createQueryBuilder('payment') - ->innerJoin('payment.attribute', 'attribute') - ->where('attribute.adyenType = :adyenCode') - ->setMaxResults(1) - ->setParameter(':adyenCode', $code); - - return $query->getQuery()->execute()[0] ?? null; - } - - private function paymentRepository(): ModelRepository - { - return $this->modelManager->getRepository(Payment::class); - } -} diff --git a/Shopware/Repository/PaymentRepositoryInterface.php b/Shopware/Repository/PaymentRepositoryInterface.php deleted file mode 100644 index 5c911dc1..00000000 --- a/Shopware/Repository/PaymentRepositoryInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -paymentMeanSerializer = $paymentMeanSerializer; - } - - public function __invoke(PaymentMeanCollection $paymentMeans): array - { - return array_reduce( - iterator_to_array($paymentMeans->getIterator()), - function(array $carry, PaymentMean $paymentMean) { - return array_merge( - array_values($carry), - array_values(($this->paymentMeanSerializer)($paymentMean)) - ); - }, - [] - ); - } -} diff --git a/Shopware/Serializer/SwPaymentMeanSerializer.php b/Shopware/Serializer/SwPaymentMeanSerializer.php deleted file mode 100644 index 0f00a3c6..00000000 --- a/Shopware/Serializer/SwPaymentMeanSerializer.php +++ /dev/null @@ -1,22 +0,0 @@ -getId() => array_replace($paymentMean->getRaw(), [ - 'name' => $paymentMean->getValue('name'), - 'description' => $paymentMean->getValue('description'), - 'additionaldescription' => $paymentMean->getValue('additionaldescription'), - ]), - ]; - } -} diff --git a/Subscriber/Account/SaveStoredMethodPreferenceSubscriber.php b/Subscriber/Account/SaveStoredMethodPreference.php similarity index 62% rename from Subscriber/Account/SaveStoredMethodPreferenceSubscriber.php rename to Subscriber/Account/SaveStoredMethodPreference.php index 30d469b2..20317e9b 100755 --- a/Subscriber/Account/SaveStoredMethodPreferenceSubscriber.php +++ b/Subscriber/Account/SaveStoredMethodPreference.php @@ -4,37 +4,31 @@ namespace AdyenPayment\Subscriber\Account; -use AdyenPayment\Components\Adyen\PaymentMethod\StoredPaymentMeanProviderInterface; -use AdyenPayment\Components\Manager\UserPreferenceManagerInterface; use AdyenPayment\Models\UserPreference; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use Enlight\Event\SubscriberInterface; use Enlight_Components_Session_Namespace; -final class SaveStoredMethodPreferenceSubscriber implements SubscriberInterface +final class SaveStoredMethodPreference implements SubscriberInterface { /** @var Enlight_Components_Session_Namespace */ private $session; - - /** @var UserPreferenceManagerInterface */ - private $userPreferenceManager; - /** @var EntityRepository */ private $userPreferenceRepository; - - /** @var StoredPaymentMeanProviderInterface */ - private $storedPaymentMeanProvider; + /** + * @var EntityManager + */ + private $modelManager; public function __construct( Enlight_Components_Session_Namespace $session, - UserPreferenceManagerInterface $userPreferenceManager, EntityRepository $userPreferenceRepository, - StoredPaymentMeanProviderInterface $storedPaymentMeanProvider + EntityManager $modelManager ) { $this->session = $session; - $this->userPreferenceManager = $userPreferenceManager; $this->userPreferenceRepository = $userPreferenceRepository; - $this->storedPaymentMeanProvider = $storedPaymentMeanProvider; + $this->modelManager = $modelManager; } public static function getSubscribedEvents(): array @@ -56,8 +50,14 @@ public function __invoke(\Enlight_Controller_ActionEventArgs $args): void return; } - $storedMethod = $this->storedPaymentMeanProvider->fromRequest($request); - $storedMethodId = null !== $storedMethod ? $storedMethod->getValue('stored_method_id') : null; + $selectedPaymentMeanId = $request->getParam('register', [])['payment'] ?? null; + if (null === $selectedPaymentMeanId) { + return; + } + + // Stored payment mean id is in format umbrellaPaymentId_storedPaymentMethodId + $storedMethodIdParts = explode('_', $selectedPaymentMeanId); + $storedMethodId = 2 === count($storedMethodIdParts) ? $storedMethodIdParts[1] : null; $userPreference = $this->userPreferenceRepository->findOneBy(['userId' => $userId]); if (null === $userPreference) { @@ -66,6 +66,7 @@ public function __invoke(\Enlight_Controller_ActionEventArgs $args): void } $userPreference = $userPreference->setStoredMethodId($storedMethodId); - $this->userPreferenceManager->save($userPreference); + $this->modelManager->persist($userPreference); + $this->modelManager->flush($userPreference); } } diff --git a/Subscriber/Checkout/AddErrorMessageToViewSubscriber.php b/Subscriber/AddErrorMessageToView.php similarity index 61% rename from Subscriber/Checkout/AddErrorMessageToViewSubscriber.php rename to Subscriber/AddErrorMessageToView.php index d8877c50..344a9c85 100755 --- a/Subscriber/Checkout/AddErrorMessageToViewSubscriber.php +++ b/Subscriber/AddErrorMessageToView.php @@ -2,12 +2,17 @@ declare(strict_types=1); -namespace AdyenPayment\Subscriber\Checkout; +namespace AdyenPayment\Subscriber; -use AdyenPayment\Session\ErrorMessageProvider; +use AdyenPayment\Components\ErrorMessageProvider; use Enlight\Event\SubscriberInterface; -final class AddErrorMessageToViewSubscriber implements SubscriberInterface +/** + * Class AddErrorMessageToView + * + * @package AdyenPayment\Subscriber + */ +final class AddErrorMessageToView implements SubscriberInterface { /** @var ErrorMessageProvider */ private $errorMessageProvider; @@ -24,10 +29,11 @@ public static function getSubscribedEvents(): array public function __invoke(\Enlight_Controller_ActionEventArgs $args): void { - if (!$this->errorMessageProvider->hasMessages()) { + if (!$this->errorMessageProvider->hasMessages() || !$args->getSubject()->View()) { return; } $args->getSubject()->View()->assign('sErrorMessages', $this->errorMessageProvider->read()); + $args->getSubject()->View()->assign('sSuccessMessages', $this->errorMessageProvider->readSuccessMessages()); } } diff --git a/Subscriber/AddExpressCheckoutToView.php b/Subscriber/AddExpressCheckoutToView.php new file mode 100755 index 00000000..5b537fa6 --- /dev/null +++ b/Subscriber/AddExpressCheckoutToView.php @@ -0,0 +1,74 @@ +session = $session; + } + + public static function getSubscribedEvents(): array + { + return [ + 'Enlight_Controller_Action_PostDispatchSecure_Frontend_Detail' => 'handleProductDetailsPage', + 'Enlight_Controller_Action_PostDispatchSecure_Frontend_Checkout' => 'handleCartPage', + ]; + } + + public function handleProductDetailsPage(Enlight_Event_EventArgs $args): void + { + if (!$this->isUserLoggedIn() || $args->getRequest()->getActionName() !== 'index') { + return; + } + + $args->getSubject()->View()->assign( + 'adyenShowExpressCheckout', true + ); + } + + public function handleCartPage(Enlight_Event_EventArgs $args): void + { + if (!$this->isUserLoggedIn() || $args->getRequest()->getActionName() !== 'cart') { + return; + } + + $args->getSubject()->View()->assign( + 'adyenShowExpressCheckout', true + ); + } + + private function isUserLoggedIn(): bool + { + if (!(bool)$this->session->get('sUserId')) { + return false; + } + + $userData = Shopware()->Modules()->Admin()->sGetUserData(); + if ( + !empty($userData['additional']['user']['accountmode']) && + (int)$userData['additional']['user']['accountmode'] === Customer::ACCOUNT_MODE_FAST_LOGIN + ) { + return false; + } + + return true; + } +} diff --git a/Subscriber/AddPluginTemplatesSubscriber.php b/Subscriber/AddPluginTemplatesSubscriber.php deleted file mode 100755 index b50f1afd..00000000 --- a/Subscriber/AddPluginTemplatesSubscriber.php +++ /dev/null @@ -1,35 +0,0 @@ -templateManager = $templateManager; - $this->pluginDirectory = $pluginDirectory; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PreDispatch' => '__invoke', - ]; - } - - public function __invoke(): void - { - $this->templateManager->addTemplateDir($this->pluginDirectory.'/Resources/views'); - } -} diff --git a/Subscriber/Applepay/MerchantAssociation/AddSeoUrlSubscriber.php b/Subscriber/AddSeoUrlSubscriber.php similarity index 63% rename from Subscriber/Applepay/MerchantAssociation/AddSeoUrlSubscriber.php rename to Subscriber/AddSeoUrlSubscriber.php index 6fb07568..0a73c8e5 100755 --- a/Subscriber/Applepay/MerchantAssociation/AddSeoUrlSubscriber.php +++ b/Subscriber/AddSeoUrlSubscriber.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace AdyenPayment\Subscriber\Applepay\MerchantAssociation; +namespace AdyenPayment\Subscriber; -use AdyenPayment\Applepay\MerchantAssociation\RewriteUrl\UrlWriter; +use AdyenPayment\Components\ApplePay\SeoUrlWriter; use Enlight\Event\SubscriberInterface; final class AddSeoUrlSubscriber implements SubscriberInterface { - /** @var UrlWriter */ + /** @var SeoUrlWriter */ private $seoUrlWriter; - public function __construct(UrlWriter $seoUrlWriter) + public function __construct(SeoUrlWriter $seoUrlWriter) { $this->seoUrlWriter = $seoUrlWriter; } @@ -21,7 +21,7 @@ public static function getSubscribedEvents(): array { return [ 'Shopware_CronJob_RefreshSeoIndex_CreateRewriteTable' => '__invoke', - 'sRewriteTable::sCreateRewriteTable::after' => '__invoke', + 'Enlight_Controller_Action_PostDispatchSecure_Backend_Seo' => '__invoke' ]; } diff --git a/Subscriber/AddStoredMethodIdOnOrderSubscriber.php b/Subscriber/AddStoredMethodIdOnOrderSubscriber.php deleted file mode 100644 index 15b6f910..00000000 --- a/Subscriber/AddStoredMethodIdOnOrderSubscriber.php +++ /dev/null @@ -1,60 +0,0 @@ -modelManager = $modelManager; - $this->paymentInfoRepository = $this->modelManager->getRepository(PaymentInfo::class); - $this->session = $session; - } - - public static function getSubscribedEvents() - { - return ['Shopware_Modules_Order_SaveOrder_ProcessDetails' => 'persistPaymentInfoStoredMethodId']; - } - - public function persistPaymentInfoStoredMethodId(Enlight_Event_EventArgs $args) - { - $paymentInfoId = $this->session->get(AdyenPayment::SESSION_ADYEN_PAYMENT_INFO_ID); - $storedMethodId = (string) $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, ''); - - if (null === $paymentInfoId) { - return $args->getReturn(); - } - - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $this->paymentInfoRepository->findOneBy([ - 'id' => $paymentInfoId, - ]); - - if ($paymentInfo) { - $paymentInfo->setStoredMethodId($storedMethodId); - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - - return $args->getReturn(); - } -} diff --git a/Subscriber/EnrichUserPreferenceSubscriber.php b/Subscriber/AddStoredMethodUserPreferenceToView.php similarity index 95% rename from Subscriber/EnrichUserPreferenceSubscriber.php rename to Subscriber/AddStoredMethodUserPreferenceToView.php index 1caa3c2e..d9ab13bd 100755 --- a/Subscriber/EnrichUserPreferenceSubscriber.php +++ b/Subscriber/AddStoredMethodUserPreferenceToView.php @@ -9,7 +9,7 @@ use Enlight\Event\SubscriberInterface; use Enlight_Components_Session_Namespace; -final class EnrichUserPreferenceSubscriber implements SubscriberInterface +final class AddStoredMethodUserPreferenceToView implements SubscriberInterface { /** @var Enlight_Components_Session_Namespace */ private $session; diff --git a/Subscriber/Applepay/MerchantAssociation/PerformanceLoaderSubscriber.php b/Subscriber/Applepay/MerchantAssociation/PerformanceLoaderSubscriber.php deleted file mode 100755 index 3e03e448..00000000 --- a/Subscriber/Applepay/MerchantAssociation/PerformanceLoaderSubscriber.php +++ /dev/null @@ -1,36 +0,0 @@ -pluginDir = $pluginDir; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Backend_Performance' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - if ('load' !== $subject->Request()->getActionName()) { - return; - } - - $subject->View()->addTemplateDir($this->pluginDir.'/Resources/views/'); - $subject->View()->extendsTemplate('backend/performance/view/applepaymerchantassociation.js'); - } -} diff --git a/Subscriber/Applepay/MerchantAssociation/RegisterUrlCountSubscriber.php b/Subscriber/Applepay/MerchantAssociation/RegisterUrlCountSubscriber.php deleted file mode 100644 index d1abdfc8..00000000 --- a/Subscriber/Applepay/MerchantAssociation/RegisterUrlCountSubscriber.php +++ /dev/null @@ -1,24 +0,0 @@ - '__invoke', - ]; - } - - public function __invoke(\Enlight_Event_EventArgs $args): array - { - return array_merge($args->getReturn(), [ - 'applepaymerchantassociation' => 1, // 1: same URL for each shop - ]); - } -} diff --git a/Subscriber/AssignPaymentMethodStateDataToSession.php b/Subscriber/AssignPaymentMethodStateDataToSession.php new file mode 100755 index 00000000..0e0d501a --- /dev/null +++ b/Subscriber/AssignPaymentMethodStateDataToSession.php @@ -0,0 +1,49 @@ +session = $session; + } + + public static function getSubscribedEvents(): array + { + return ['Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke']; + } + + public function __invoke(\Enlight_Controller_ActionEventArgs $args): void + { + if ('payment' !== $args->getRequest()->getActionName()) { + return; + } + + $this->session->offsetSet( + 'adyenPaymentMethodStateData', + $args->getRequest()->get('adyenPaymentMethodStateData') + ); + $this->session->offsetSet( + 'adyenIsXHR', + $args->getRequest()->getParam('isXHR') + ); + } +} diff --git a/Subscriber/Checkout/PersistStoredMethodIdSubscriber.php b/Subscriber/AssignStoredPaymentMethodToSession.php similarity index 58% rename from Subscriber/Checkout/PersistStoredMethodIdSubscriber.php rename to Subscriber/AssignStoredPaymentMethodToSession.php index 446aa712..d7e3a888 100755 --- a/Subscriber/Checkout/PersistStoredMethodIdSubscriber.php +++ b/Subscriber/AssignStoredPaymentMethodToSession.php @@ -2,25 +2,19 @@ declare(strict_types=1); -namespace AdyenPayment\Subscriber\Checkout; +namespace AdyenPayment\Subscriber; -use AdyenPayment\AdyenPayment; use Enlight\Event\SubscriberInterface; use Enlight_Components_Session_Namespace; -use Shopware_Components_Modules; -final class PersistStoredMethodIdSubscriber implements SubscriberInterface +final class AssignStoredPaymentMethodToSession implements SubscriberInterface { /** @var Enlight_Components_Session_Namespace */ private $session; - /** @var Shopware_Components_Modules */ - private $modules; - - public function __construct(Enlight_Components_Session_Namespace $session, Shopware_Components_Modules $modules) + public function __construct(Enlight_Components_Session_Namespace $session) { $this->session = $session; - $this->modules = $modules; } public static function getSubscribedEvents(): array @@ -38,11 +32,11 @@ public function __invoke(\Enlight_Controller_ActionEventArgs $args): void return; } - $storedMethodId = $args->getRequest()->getParam(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID); - $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, $storedMethodId); + $storedMethodId = $args->getRequest()->getParam('adyenStoredPaymentMethodId'); + $this->session->offsetSet('adyenStoredPaymentMethodId', $storedMethodId); if ($storedMethodId) { - $this->modules->Admin()->sUpdatePayment( - str_replace("_${storedMethodId}", '', $args->getRequest()->getPost('payment')) + Shopware()->Modules()->Admin()->sUpdatePayment( + str_replace("_$storedMethodId", '', $args->getRequest()->getPost('payment')) ); } } diff --git a/Subscriber/Backend/BackendIndex.php b/Subscriber/Backend/BackendIndex.php new file mode 100644 index 00000000..3155e691 --- /dev/null +++ b/Subscriber/Backend/BackendIndex.php @@ -0,0 +1,40 @@ + 'onPostDispatchBackendIndex' + ]; + } + + /** + * @param \Enlight_Controller_ActionEventArgs $args + */ + public function onPostDispatchBackendIndex($args) + { + $action = $args->getSubject(); + $request = $action->Request(); + $response = $action->Response(); + $view = $action->View(); + + if ($request->getActionName() === 'load') { + $view->extendsTemplate('backend/_resources/js/AdyenShopNotifications.js'); + } + + if (!$request->isDispatched() + || $response->isException() + || $request->getActionName() !== 'index' + || !$view->hasTemplate() + ) { + return; + } + + $view->extendsTemplate('backend/index/adyen_header.tpl'); + } +} diff --git a/Subscriber/Backend/BackendJavascriptSubscriber.php b/Subscriber/Backend/BackendJavascriptSubscriber.php deleted file mode 100755 index b9a29801..00000000 --- a/Subscriber/Backend/BackendJavascriptSubscriber.php +++ /dev/null @@ -1,93 +0,0 @@ -pluginDirectory = $pluginDirectory; - $this->notificationRepository = $notificationRepository; - } - - /** - * @return string[] - * - * @psalm-return array{Enlight_Controller_Action_PostDispatchSecure_Backend_Order: 'onOrderPostDispatch', - * Enlight_Controller_Action_PostDispatchSecure_Backend_Customer: 'onCustomerPostDispatch'} - */ - public static function getSubscribedEvents() - { - return [ - 'Enlight_Controller_Action_PostDispatchSecure_Backend_Order' => 'onOrderPostDispatch', - 'Enlight_Controller_Action_PostDispatchSecure_Backend_Customer' => 'onCustomerPostDispatch', - ]; - } - - public function onOrderPostDispatch(Enlight_Event_EventArgs $args): void - { - /** @var \Shopware_Controllers_Backend_Customer $controller */ - $controller = $args->getSubject(); - - $view = $controller->View(); - $request = $controller->Request(); - - $view->addTemplateDir($this->pluginDirectory.'/Resources/views'); - - if ('index' === $request->getActionName()) { - $view->extendsTemplate('backend/order/adyen_payment_method/app.js'); - } - - if ('getList' === $request->getActionName()) { - $this->onGetList($args); - } - } - - public function onCustomerPostDispatch(Enlight_Event_EventArgs $args): void - { - /** @var \Shopware_Controllers_Backend_Customer $controller */ - $controller = $args->getSubject(); - - $view = $controller->View(); - $request = $controller->Request(); - - $view->addTemplateDir($this->pluginDirectory.'/Resources/views'); - - if ('index' === $request->getActionName()) { - $view->extendsTemplate('backend/customer/adyen_payment_method/app.js'); - } - - if ('getOrders' === $request->getActionName()) { - $this->onGetList($args); - } - } - - private function onGetList(Enlight_Event_EventArgs $args): void - { - $assign = $args->getSubject()->View()->getAssign(); - - $data = $assign['data']; - foreach ($data as &$order) { - $notification = $this->notificationRepository->findOneBy(['orderId' => $order['id']]); - - if (!$notification) { - continue; - } - $order['adyen_payment_order_payment'] = $notification->getPaymentMethod(); - } - - $args->getSubject()->View()->assign('data', $data); - } -} diff --git a/Subscriber/Backend/BackendOrderSubscriber.php b/Subscriber/Backend/BackendOrderSubscriber.php deleted file mode 100755 index 6020eb66..00000000 --- a/Subscriber/Backend/BackendOrderSubscriber.php +++ /dev/null @@ -1,86 +0,0 @@ -paymentInfoRepository = $paymentInfoRepository; - $this->notificationManager = $notificationManager; - } - - /** - * @return string[] - * - * @psalm-return array{Enlight_Controller_Action_PostDispatchSecure_Backend_Order: 'onBackendOrder'} - */ - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatchSecure_Backend_Order' => '__invoke', - ]; - } - - public function __invoke(Enlight_Event_EventArgs $args): void - { - /** @var Shopware_Controllers_Backend_Order $subject */ - $subject = $args->getSubject(); - - if ('getList' !== $subject->Request()->getActionName()) { - return; - } - - $data = $subject->View()->getAssign('data'); - - $this->addTransactionData($data); - - $subject->View()->assign('data', $data); - } - - /** - * @throws \Doctrine\ORM\NonUniqueResultException - */ - private function addTransactionData(array &$data): void - { - foreach ($data as &$order) { - $order['adyenTransaction'] = null; - $order['adyenNotification'] = null; - $order['adyenRefundable'] = false; - - $source = (int) ($order['payment']['source'] ?? null); - if (!SourceType::load($source)->equals(SourceType::adyen())) { - continue; - } - - $lastNotification = $this->notificationManager->getLastNotificationForOrderId($order['id']); - if ($lastNotification) { - $transaction = $this->paymentInfoRepository->findOneBy(['orderId' => $order['id']]); - if ($transaction) { - $order['adyenTransaction'] = $transaction; - } - - $order['adyenNotification'] = $lastNotification; - $order['adyenRefundable'] = in_array($lastNotification->getEventCode(), [ - 'AUTHORISATION', - 'CAPTURE', - ], true); - } - } - } -} diff --git a/Subscriber/Backend/ExtendViewTemplateSubscriber.php b/Subscriber/Backend/ExtendViewTemplateSubscriber.php deleted file mode 100644 index 9a893bdf..00000000 --- a/Subscriber/Backend/ExtendViewTemplateSubscriber.php +++ /dev/null @@ -1,38 +0,0 @@ - '__invoke', - ]; - } - - public function __invoke(\Enlight_Event_EventArgs $args): void - { - /** @var \Shopware_Controllers_Backend_Order $controller */ - $controller = $args->getSubject(); - - $view = $controller->View(); - $request = $controller->Request(); - - if ('index' === $request->getActionName()) { - $view->extendsTemplate('backend/adyen_payment_order/app.js'); - } - - if ('load' === $request->getActionName()) { - $view->extendsTemplate('backend/adyen_payment_order/view/detail/window.js'); - $view->extendsTemplate('backend/adyen_payment_order/model/order.js'); - } - } -} diff --git a/Subscriber/Backend/HideStoredPaymentsSubscriber.php b/Subscriber/Backend/HideStoredPaymentsSubscriber.php index 9416eef0..a3bec436 100644 --- a/Subscriber/Backend/HideStoredPaymentsSubscriber.php +++ b/Subscriber/Backend/HideStoredPaymentsSubscriber.php @@ -4,7 +4,7 @@ namespace AdyenPayment\Subscriber\Backend; -use AdyenPayment\Collection\Payment\PaymentMeanCollection; +use AdyenPayment\Utilities\Plugin; use Enlight\Event\SubscriberInterface; use Symfony\Component\HttpFoundation\Response; @@ -30,11 +30,7 @@ public function __invoke(\Enlight_Controller_ActionEventArgs $args): void return; } - $data = PaymentMeanCollection::createFromShopwareArray($data) - ->filterExcludeHidden() - ->toShopwareArray(); - - $args->getSubject()->View()->assign('data', array_values($data)); + $args->getSubject()->View()->assign('data', $this->filterHiddenPaymentMeans($data)); } private function isSuccessGetPaymentAction(\Enlight_Controller_ActionEventArgs $args): bool @@ -49,4 +45,17 @@ private function isSuccessGetPaymentAction(\Enlight_Controller_ActionEventArgs $ return true; } + + private function filterHiddenPaymentMeans(array $paymentMeans): array + { + return array_values(array_filter( + array_map(static function (array $paymentMean) { + if (!Plugin::isAdyenPaymentMean($paymentMean['name'])) { + return $paymentMean; + } + + return !$paymentMean['hide'] ? $paymentMean : null; + }, $paymentMeans) + )); + } } diff --git a/Subscriber/Backend/ImportSubShopPaymentMethodsSubscriber.php b/Subscriber/Backend/ImportSubShopPaymentMethodsSubscriber.php deleted file mode 100644 index e6f32a78..00000000 --- a/Subscriber/Backend/ImportSubShopPaymentMethodsSubscriber.php +++ /dev/null @@ -1,85 +0,0 @@ -shopRepository = $shopRepository; - $this->mainShopConfigRuleChain = $mainShopConfigRule; - $this->paymentMeansSubShopsWriter = $paymentMeansSubShopsWriter; - $this->paymentMethodImporter = $paymentMethodImporter; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Backend_Config' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Event_EventArgs $args): void - { - $request = $args->get('request') ?? false; - $response = $args->get('response') ?? false; - - if (!$request || !$response) { - return; - } - - if (!$this->isNewSubShopAdded($request->getParam('id'), $response, $request->getActionName())) { - return; - } - - /** @var Shop $shop */ - $shop = $this->shopRepository->findBy([], ['id' => 'desc'], 1)[0] ?? null; - if (null === $shop) { - return; - } - - $mainShop = $this->shopRepository->find(1); - if (($this->mainShopConfigRuleChain)($shop, $mainShop)) { - $this->paymentMeansSubShopsWriter->registerAdyenPaymentMethodForSubShop($shop->getId()); - - return; - } - - $this->paymentMethodImporter->importForShop($shop); - } - - private function isNewSubShopAdded($id, $response, string $action): bool - { - return null === $id - && Response::HTTP_OK === $response->getHttpResponseCode() - && self::SAVE_VALUES_ACTION === $action; - } -} diff --git a/Subscriber/Backend/OrderUpdate.php b/Subscriber/Backend/OrderUpdate.php new file mode 100644 index 00000000..4c1c782c --- /dev/null +++ b/Subscriber/Backend/OrderUpdate.php @@ -0,0 +1,156 @@ +orderRepository = $orderRepository; + } + + /** + * @inheritDoc + */ + public static function getSubscribedEvents(): array + { + return [ + 'Shopware_Controllers_Backend_OrderState_Notify' => 'backendUpdate', + 'sOrder::setPaymentStatus::after' => 'paymentStatusUpdate' + ]; + } + + /** + * @param Enlight_Hook_HookArgs $args + * + * @return void + * + * @throws CurrencyMismatchException + * @throws InvalidCurrencyCode + * @throws InvalidMerchantReferenceException + */ + public function paymentStatusUpdate(Enlight_Hook_HookArgs $args): void + { + $orderId = $args->get('orderId') ?? 0; + $paymentStatusId = (int)$args->get('paymentStatusId') ?? 0; + + $this->process($orderId, $paymentStatusId); + } + + /** + * @throws InvalidMerchantReferenceException + * @throws InvalidCurrencyCode + * @throws Exception + */ + public function backendUpdate(Enlight_Event_EventArgs $args): void + { + $orderId = $args->get('id') ?? 0; + $paymentStatusId = $args->get('status') ?? 0; + + $this->process($orderId, $paymentStatusId); + } + + /** + * @param int $orderId + * @param int $paymentStatusId + * + * @return void + * + * @throws InvalidCurrencyCode + * @throws InvalidMerchantReferenceException + * @throws CurrencyMismatchException + * @throws Exception + */ + private function process(int $orderId, int $paymentStatusId) + { + $order = $this->tryToGetShopOrderWithAdyenPayment($orderId); + if (!$order) { + return; + } + + $storeId = (string)$order->getShop()->getId(); + $generalSettings = AdminAPI::get()->generalSettings($storeId)->getGeneralSettings(); + /** @var TransactionHistory $transactionHistory */ + $transactionHistory = StoreContext::doWithStore( + $storeId, + [$this->getService(), 'getTransactionHistory'], + [$order->getTemporaryId()] + ); + $authorisedAmount = $transactionHistory->getTotalAmountForEventCode(EventCodes::AUTHORISATION); + $cancelledAmount = $transactionHistory->getTotalAmountForEventCode(EventCodes::CANCELLATION); + $capturedAmount = $transactionHistory->getCapturedAmount(); + + if ($generalSettings->toArray()['capture'] !== CaptureType::MANUAL || $generalSettings->toArray()['shipmentStatus'] !== (string)$paymentStatusId || $authorisedAmount->getPriceInCurrencyUnits() === $capturedAmount->plus($cancelledAmount)->getPriceInCurrencyUnits()) { + return; + } + + AdminAPI::get()->capture($storeId)->handle( + $order->getTemporaryId(), + $authorisedAmount->minus($cancelledAmount->plus($capturedAmount))->getPriceInCurrencyUnits(), + $order->getCurrency() + ); + } + + private function tryToGetShopOrderWithAdyenPayment(int $orderId): ?Order + { + try { + $order = $this->orderRepository->getOrderById($orderId); + + if (!$order || !Plugin::isAdyenPaymentMean($order->getPayment()->getName())) { + return null; + } + + return $order; + } catch (\Throwable $exception) { + return null; + } + } + + /** + * @return TransactionHistoryService + */ + private function getService(): TransactionHistoryService + { + if ($this->historyService === null) { + $this->historyService = ServiceRegister::getService(TransactionHistoryService::class); + } + + return $this->historyService; + } +} diff --git a/Subscriber/Backend/RemoveSubShopPaymentMethodSubscriber.php b/Subscriber/Backend/RemoveSubShopPaymentMethodSubscriber.php deleted file mode 100755 index 53eac2c6..00000000 --- a/Subscriber/Backend/RemoveSubShopPaymentMethodSubscriber.php +++ /dev/null @@ -1,52 +0,0 @@ -paymentMeanSubShopRemover = $paymentMeanSubShopRemover; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Backend_Config' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Event_EventArgs $args): void - { - $request = $args->get('request') ?? false; - $response = $args->get('response') ?? false; - - if (!$request || !$response) { - return; - } - - if (!$this->isSubShopDeleted($request->getParam('id'), $response, $request->getActionName())) { - return; - } - - $this->paymentMeanSubShopRemover->removeBySubShopId($request->getParam('id')); - } - - private function isSubShopDeleted($id, $response, string $action): bool - { - return null !== $id - && Response::HTTP_OK === $response->getHttpResponseCode() - && self::DELETE_VALUES_ACTION === $action; - } -} diff --git a/Subscriber/BackendOrderSubscriber.php b/Subscriber/BackendOrderSubscriber.php new file mode 100644 index 00000000..93e5722d --- /dev/null +++ b/Subscriber/BackendOrderSubscriber.php @@ -0,0 +1,95 @@ + 'addTransactionData']; + } + + public function addTransactionData(Enlight_Event_EventArgs $args): void + { + /** @var Shopware_Controllers_Backend_Order $subject */ + $subject = $args->getSubject(); + + if ('getList' !== $subject->Request()->getActionName()) { + return; + } + + $data = $subject->View()->getAssign('data'); + + $this->addData($data); + + $subject->View()->assign('data', $data); + } + + private function addData(array &$data): void + { + $merchantReferences = []; + $adyenOrders = []; + + foreach ($data as &$order) { + if (!isset($order['payment']['name']) || !Plugin::isAdyenPaymentMean($order['payment']['name'])) { + continue; + } + + $merchantReferences[$order['shopId']][] = $order['temporaryId']; + $adyenOrders[$order['shopId']][] = &$order; + } + + unset($order); + + foreach ($merchantReferences as $storeId => $references) { + $historyItems = StoreContext::doWithStore( + $storeId, + [$this->getService(), 'getTransactionHistoriesByReferences'], + [$references] + ); + + foreach ($adyenOrders[$storeId] as &$order) { + $order['adyenTransaction'] = false; + + foreach ($historyItems as $item) { + if ($item->getMerchantReference() !== $order['temporaryId']) { + continue; + } + + $order['adyenTransaction'] = $item->isLive() !== null; + break; + } + } + + unset($order); + } + } + + /** + * @return TransactionHistoryService + */ + private function getService(): TransactionHistoryService + { + if ($this->historyService === null) { + $this->historyService = ServiceRegister::getService(TransactionHistoryService::class); + } + + return $this->historyService; + } +} diff --git a/Subscriber/BootstrapRegistration.php b/Subscriber/BootstrapRegistration.php new file mode 100644 index 00000000..29ca387e --- /dev/null +++ b/Subscriber/BootstrapRegistration.php @@ -0,0 +1,30 @@ + 'registerBootstrap', + 'Shopware_Console_Add_Command' => 'registerBootstrap', + ]; + } + + /** + * Initializes bootstrap. + */ + public function registerBootstrap() + { + Bootstrap::init(); + } +} diff --git a/Subscriber/Checkout/AddAdyenSourceTypeToViewSubscriber.php b/Subscriber/Checkout/AddAdyenSourceTypeToViewSubscriber.php deleted file mode 100644 index c2af4997..00000000 --- a/Subscriber/Checkout/AddAdyenSourceTypeToViewSubscriber.php +++ /dev/null @@ -1,29 +0,0 @@ - '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - - if ('confirm' !== $subject->Request()->getActionName()) { - return; - } - - $subject->View()->assign('adyenSourceType', SourceType::adyen()->getType()); - } -} diff --git a/Subscriber/Checkout/AddApplePayConfigToViewSubscriber.php b/Subscriber/Checkout/AddApplePayConfigToViewSubscriber.php deleted file mode 100644 index ea70e4f5..00000000 --- a/Subscriber/Checkout/AddApplePayConfigToViewSubscriber.php +++ /dev/null @@ -1,22 +0,0 @@ -paymentType = PaymentType::applePay(); - $this->paymentConfigViewKey = 'sAdyenApplePayConfig'; - } -} diff --git a/Subscriber/Checkout/AddGooglePayConfigToViewSubscriber.php b/Subscriber/Checkout/AddGooglePayConfigToViewSubscriber.php deleted file mode 100644 index 0961d8e5..00000000 --- a/Subscriber/Checkout/AddGooglePayConfigToViewSubscriber.php +++ /dev/null @@ -1,22 +0,0 @@ -paymentType = PaymentType::googlePay(); - $this->paymentConfigViewKey = 'sAdyenGoogleConfig'; - } -} diff --git a/Subscriber/Checkout/BaseAddPaymentMethodConfigToViewSubscriber.php b/Subscriber/Checkout/BaseAddPaymentMethodConfigToViewSubscriber.php deleted file mode 100644 index 159c38ff..00000000 --- a/Subscriber/Checkout/BaseAddPaymentMethodConfigToViewSubscriber.php +++ /dev/null @@ -1,81 +0,0 @@ -paymentMethodConfigProvider = $paymentMethodConfigProvider; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - if ('confirm' !== $subject->Request()->getActionName()) { - return; - } - - $userData = $subject->View()->getAssign('sUserData'); - $paymentMean = PaymentMean::createFromShopwareArray($userData['additional']['payment'] ?? []); - if (!$paymentMean->getSource()->equals(SourceType::adyen())) { - return; - } - - $basket = $subject->View()->getAssign('sBasket'); - if (!$basket) { - return; - } - - $paymentType = $paymentMean->adyenType(); - if (!$paymentType || !$paymentType->equals($this->getPaymentType())) { - return; - } - - $paymentConfig = ($this->paymentMethodConfigProvider)(ConfigContext::fromCheckoutEvent($args)); - $subject->View()->assign( - $this->getPaymentMethodViewKey(), - Sanitize::escape(JsonUtil::encode($paymentConfig)) - ); - } - - protected function getPaymentType(): PaymentType - { - return $this->paymentType; - } - - protected function getPaymentMethodViewKey(): string - { - return $this->paymentConfigViewKey; - } -} diff --git a/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriber.php b/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriber.php deleted file mode 100755 index 2f23dfb8..00000000 --- a/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriber.php +++ /dev/null @@ -1,83 +0,0 @@ -session = $session; - $this->paymentMeansProvider = $paymentMeansProvider; - } - - public static function getSubscribedEvents(): array - { - return ['Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke']; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - $actionName = $args->getRequest()->getActionName(); - $isShippingPaymentView = 'shippingPayment' === $actionName && !$args->getRequest()->getParam('isXHR'); - if (!$isShippingPaymentView) { - return; - } - - $enrichedPaymentMeans = PaymentMeanCollection::createFromShopwareArray(($this->paymentMeansProvider)()); - $userData = $subject->View()->getAssign('sUserData'); - - // if the stored method is not saved in session it means it was not selected in the payment step - $storedMethodId = $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID); - if (null === $storedMethodId) { - $preselectedPaymentId = $userData['additional']['payment']['id'] ?? null; - if (null === $preselectedPaymentId) { - return; - } - - $umbrellaPayment = $enrichedPaymentMeans->fetchStoredMethodUmbrellaPaymentMean(); - if (null === $umbrellaPayment) { - // guest user won't have stored method - return; - } - // but if the umbrella payment is in the user data it means a stored method was preselected by the user - if ($umbrellaPayment->getId() !== (int) $preselectedPaymentId) { - return; - } - // we use the saved user preference to get the stored method and allow the rest of the flow work normally - $storedMethodId = $args->getSubject()->View()->getAssign('adyenUserPreference')['storedMethodId'] ?? null; - } - - if (null === $storedMethodId) { - return; - } - - $paymentMean = $enrichedPaymentMeans->fetchByStoredMethodId($storedMethodId); - if (null === $paymentMean) { - return; - } - - $userData = $subject->View()->getAssign('sUserData'); - $userData['additional']['payment'] = $paymentMean->getRaw(); - $subject->View()->assign('sUserData', $userData); - $formData = $subject->View()->getAssign('sFormData'); - $formData['payment'] = $paymentMean->getValue('stored_method_umbrella_id'); - $subject->View()->assign('sFormData', $formData); - } -} diff --git a/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriber.php b/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriber.php deleted file mode 100755 index 66573598..00000000 --- a/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriber.php +++ /dev/null @@ -1,73 +0,0 @@ -enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; - $this->paymentMeansProvider = $paymentMeansProvider; - $this->session = $session; - } - - public static function getSubscribedEvents(): array - { - return [ - // run as early as possible, before any BaseAddPaymentMethodConfigToViewSubscriber implementations - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => ['__invoke', -99999], - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - if ('confirm' !== $args->getRequest()->getActionName()) { - return; - } - - $storedMethodId = $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID); - $userData = $subject->View()->getAssign('sUserData'); - $paymentMeanId = $userData['additional']['payment']['id'] ?? null; - - if (null === $storedMethodId && null === $paymentMeanId) { - return; - } - - $enrichedPaymentMeans = ($this->enrichedPaymentMeanProvider)( - PaymentMeanCollection::createFromShopwareArray(($this->paymentMeansProvider)()) - ); - - $paymentMean = null === $storedMethodId - ? $enrichedPaymentMeans->fetchById((int) $paymentMeanId) - : $enrichedPaymentMeans->fetchByStoredMethodId($storedMethodId); - - if (null === $paymentMean) { - return; - } - - $userData['additional']['payment'] = $paymentMean->getRaw(); - $subject->View()->assign('sUserData', $userData); - } -} diff --git a/Subscriber/Checkout/RegisterConfirmSnippetsSubscriber.php b/Subscriber/Checkout/RegisterConfirmSnippetsSubscriber.php deleted file mode 100644 index e64b8d49..00000000 --- a/Subscriber/Checkout/RegisterConfirmSnippetsSubscriber.php +++ /dev/null @@ -1,66 +0,0 @@ -snippets = $snippets; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - - if ('confirm' !== $subject->Request()->getActionName()) { - return; - } - - $errorSnippets = $this->snippets->getNamespace('adyen/checkout/error'); - - $snippets = []; - $snippets['errorTransactionCancelled'] = $errorSnippets->get( - 'errorTransactionCancelled', - 'Your transaction was cancelled by the Payment Service Provider.', - true - ); - $snippets['errorTransactionProcessing'] = $errorSnippets->get( - 'errorTransactionProcessing', - 'An error occured while processing your payment.', - true - ); - $snippets['errorTransactionRefused'] = $errorSnippets->get( - 'errorTransactionRefused', - 'Your transaction was refused by the Payment Service Provider.', - true - ); - $snippets['errorTransactionUnknown'] = $errorSnippets->get( - 'errorTransactionUnknown', - 'Your transaction was cancelled due to an unknown reason.', - true - ); - $snippets['errorGooglePayNotAvailable'] = $errorSnippets->get( - 'errorGooglePayNotAvailable', - 'Google Pay is currently not available.', - true - ); - - $subject->View()->assign('mAdyenSnippets', htmlentities(json_encode($snippets))); - } -} diff --git a/Subscriber/Checkout/RegisterPaymentSnippetsSubscriber.php b/Subscriber/Checkout/RegisterPaymentSnippetsSubscriber.php deleted file mode 100755 index 38e14a81..00000000 --- a/Subscriber/Checkout/RegisterPaymentSnippetsSubscriber.php +++ /dev/null @@ -1,57 +0,0 @@ -snippets = $snippets; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - - if (!in_array($subject->Request()->getActionName(), ['shippingPayment', 'saveShippingPayment'], true)) { - return; - } - - $paymentSnippets = $this->snippets->getNamespace('adyen/checkout/payment'); - - $snippets = [ - 'updatePaymentInformation' => $paymentSnippets->get( - 'updatePaymentInformation', - 'Update your payment information', - true - ), - 'storedPaymentMethodTitle' => $paymentSnippets->get( - 'storedPaymentMethodTitle', - 'Stored payment methods', - true - ), - 'paymentMethodTitle' => $paymentSnippets->get( - 'paymentMethodTitle', - 'Payment methods', - true - ), - ]; - - $subject->View()->assign('mAdyenSnippets', htmlentities(json_encode($snippets))); - } -} diff --git a/Subscriber/CheckoutSubscriber.php b/Subscriber/CheckoutSubscriber.php deleted file mode 100755 index 8ab89953..00000000 --- a/Subscriber/CheckoutSubscriber.php +++ /dev/null @@ -1,156 +0,0 @@ -configuration = $configuration; - $this->paymentMethodService = $paymentMethodService; - $this->dataConversion = $dataConversion; - $this->enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; - $this->paymentMethodOptionsBuilder = $paymentMethodOptionsBuilder; - $this->paymentMeanCollectionSerializer = $paymentMeanCollectionSerializer; - $this->checkoutBasketProvider = $checkoutBasketProvider; - } - - public static function getSubscribedEvents(): array - { - return [ - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke', - ]; - } - - public function __invoke(\Enlight_Controller_ActionEventArgs $args): void - { - $subject = $args->getSubject(); - - $this->checkBasketAmount($subject); - $this->checkFirstCheckoutStep($subject); - } - - private function checkBasketAmount(\Enlight_Controller_Action $subject): void - { - if ('finish' === $subject->Request()->getActionName()) { - return; - } - - $userData = $subject->View()->getAssign('sUserData'); - - $paymentMean = PaymentMean::createFromShopwareArray($userData['additional']['payment'] ?? []); - if (!$paymentMean->getSource()->equals(SourceType::adyen())) { - return; - } - - $basket = ($this->checkoutBasketProvider)(); - - if (!$basket) { - return; - } - $value = $basket['sAmount']; - if (empty($value)) { - $this->revertToDefaultPaymentMethod($subject); - } - } - - private function checkFirstCheckoutStep(\Enlight_Controller_Action $subject): void - { - if ('confirm' !== $subject->Request()->getActionName()) { - return; - } - - if ($this->shouldRedirectToStep2($subject)) { - $subject->forward('shippingPayment', 'checkout'); - } - } - - private function shouldRedirectToStep2(\Enlight_Controller_Action $subject): bool - { - $userData = $subject->View()->getAssign('sUserData'); - $swPaymentMean = PaymentMean::createFromShopwareArray($userData['additional']['payment'] ?? []); - if (!$swPaymentMean->isAdyenSourceType()) { - return false; - } - - $paymentMethodOptions = ($this->paymentMethodOptionsBuilder)(); - if (0 === (int) $paymentMethodOptions['value']) { - return false; - } - - $adyenPaymentMethods = $this->paymentMethodService->getPaymentMethods( - $paymentMethodOptions['countryCode'], - $paymentMethodOptions['currency'], - $paymentMethodOptions['value'] - ); - - $adyenPaymentMethod = $adyenPaymentMethods->fetchByPaymentMean($swPaymentMean); - if (!$adyenPaymentMethod) { - return true; - } - - // @TODO Adyen Checkout API 68 'details' are removed - if (!$adyenPaymentMethod->hasDetails() && !$adyenPaymentMethod->isStoredPayment()) { - $subject->View()->assign('adyenPaymentState', $adyenPaymentMethod->serializeMinimalState()); - - return false; - } - - return false; - } - - private function revertToDefaultPaymentMethod(\Enlight_Controller_Action $subject): void - { - $defaultPaymentId = Shopware()->Config()->get('defaultPayment'); - $defaultPayment = Shopware()->Modules()->Admin()->sGetPaymentMeanById($defaultPaymentId); - if (Shopware()->Modules()->Admin()->sUpdatePayment($defaultPaymentId)) { - // Replace Adyen payment method in the template with the default payment method. - $userData = $subject->View()->getAssign('sUserData'); - $userData['additional']['payment'] = $defaultPayment; - $subject->View()->assign('sUserData', $userData); - $subject->View()->assign('sPayment', $defaultPayment); - $subject->View()->clearAssign('adyenPaymentState'); - } - } -} diff --git a/Subscriber/ControllerPath.php b/Subscriber/ControllerPath.php new file mode 100644 index 00000000..99c5d913 --- /dev/null +++ b/Subscriber/ControllerPath.php @@ -0,0 +1,51 @@ +pluginDirectory = $pluginDirectory; + } + + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Dispatcher_ControllerPath_Backend_AdyenPaymentMain' => 'onGetControllerPromotion', + 'Enlight_Controller_Dispatcher_ControllerPath_Backend_AdyenAuthorization' => 'onGetControllerPromotion', + 'Enlight_Controller_Dispatcher_ControllerPath_Frontend_AdyenAsyncProcess' => 'onGetControllerPromotion', + 'Enlight_Controller_Dispatcher_ControllerPath_Frontend_AdyenWebhook' => 'onGetControllerPromotion', + 'Enlight_Controller_Dispatcher_ControllerPath_Frontend_AdyenPaymentProcess' => 'onGetControllerPromotion', + 'Enlight_Controller_Dispatcher_ControllerPath_Frontend_AdyenDonations' => 'onGetControllerPromotion', + 'Shopware_Controllers_Frontend_AdyenExpressCheckout' => 'onGetControllerPromotion', + ]; + } + + + /** + * Controller path handler, generates controller path based on a event name + * + * @param \Enlight_Event_EventArgs $arguments + * @return string Controller path + */ + public function onGetControllerPromotion(\Enlight_Event_EventArgs $arguments): string + { + $eventName = $arguments->getName(); + + $moduleAndController = str_replace('Enlight_Controller_Dispatcher_ControllerPath_', '', $eventName); + list($module, $controller) = explode('_', $moduleAndController); + + return "{$this->pluginDirectory}/Controllers/{$module}/{$controller}.php"; + } +} diff --git a/Subscriber/Cronjob/ImportPaymentMethodSubscriber.php b/Subscriber/Cronjob/ImportPaymentMethodSubscriber.php deleted file mode 100755 index b008009e..00000000 --- a/Subscriber/Cronjob/ImportPaymentMethodSubscriber.php +++ /dev/null @@ -1,32 +0,0 @@ -paymentMethodImporter = $paymentMethodImporter; - } - - public static function getSubscribedEvents(): array - { - return [ - Event::cronImportPaymentMethods()->getName() => '__invoke', - ]; - } - - public function __invoke(\Shopware_Components_Cron_CronJob $job): void - { - iterator_to_array($this->paymentMethodImporter->importAll()); - } -} diff --git a/Subscriber/Cronjob/ProcessNotifications.php b/Subscriber/Cronjob/ProcessNotifications.php deleted file mode 100644 index ac5e0d36..00000000 --- a/Subscriber/Cronjob/ProcessNotifications.php +++ /dev/null @@ -1,99 +0,0 @@ -fifoNotificationLoader = $fifoNotificationLoader; - $this->fifoTextNotificationLoader = $fifoTextNotificationLoader; - $this->notificationProcessor = $notificationProcessor; - $this->incomingNotificationManager = $incomingNotificationManager; - $this->logger = $logger; - } - - /** - * @return string[] - * - * @psalm-return array - */ - public static function getSubscribedEvents() - { - return [ - Event::cronProcessNotifications()->getName() => 'runCronjob', - ]; - } - - /** - * @throws \Doctrine\ORM\ORMException - * @throws \Enlight_Event_Exception - */ - public function runCronjob(Shopware_Components_Cron_CronJob $job): void - { - $textNotifications = $this->fifoTextNotificationLoader->get(); - $this->incomingNotificationManager->convertNotifications($textNotifications); - - /** @var \Generator $feedback */ - $feedback = $this->notificationProcessor->processMany( - $this->fifoNotificationLoader->load(self::NUMBER_OF_NOTIFICATIONS_TO_HANDLE) - ); - - /** @var NotificationProcessorFeedback $item */ - foreach ($feedback as $item) { - if (!$item->isSuccess()) { - $this->logger->alert($item->getNotification()->getId().': '.$item->getMessage()); - } - } - } -} diff --git a/Subscriber/EnrichPaymentSubscriber.php b/Subscriber/EnrichPaymentSubscriber.php index 9cd98397..2e08329e 100755 --- a/Subscriber/EnrichPaymentSubscriber.php +++ b/Subscriber/EnrichPaymentSubscriber.php @@ -4,26 +4,32 @@ namespace AdyenPayment\Subscriber; -use AdyenPayment\Collection\Payment\PaymentMeanCollection; -use AdyenPayment\Components\Adyen\PaymentMethod\EnrichedPaymentMeanProviderInterface; -use AdyenPayment\Serializer\PaymentMeanCollectionSerializer; +use Adyen\Core\BusinessLogic\Domain\Checkout\PaymentRequest\Models\PaymentMethodCode; +use Adyen\Core\BusinessLogic\Domain\Multistore\StoreContext; +use Adyen\Core\BusinessLogic\Domain\Payment\Repositories\PaymentMethodConfigRepository; +use Adyen\Core\Infrastructure\ServiceRegister; +use AdyenPayment\AdyenPayment; +use AdyenPayment\Components\PaymentMeansEnricher; use Enlight\Event\SubscriberInterface; use Enlight_Event_EventArgs; +use Exception; +use Shopware\Models\Customer\Customer; +/** + * Class EnrichPaymentSubscriber + * + * @package AdyenPayment\Subscriber + */ final class EnrichPaymentSubscriber implements SubscriberInterface { - /** @var EnrichedPaymentMeanProviderInterface */ - private $enrichedPaymentMeanProvider; - - /** @var PaymentMeanCollectionSerializer */ - private $serializer; - - public function __construct( - EnrichedPaymentMeanProviderInterface $enrichedPaymentMeanProvider, - PaymentMeanCollectionSerializer $serializer - ) { - $this->enrichedPaymentMeanProvider = $enrichedPaymentMeanProvider; - $this->serializer = $serializer; + /** + * @var PaymentMeansEnricher + */ + private $paymentMeansEnricher; + + public function __construct(PaymentMeansEnricher $paymentMeansEnricher) + { + $this->paymentMeansEnricher = $paymentMeansEnricher; } public static function getSubscribedEvents(): array @@ -35,18 +41,53 @@ public static function getSubscribedEvents(): array /** * @return array> + * + * @throws Exception */ public function __invoke(Enlight_Event_EventArgs $args): array { - $shopwareMethods = $args->getReturn(); + $paymentMeans = $args->getReturn(); if (!in_array(Shopware()->Front()->Request()->getActionName(), ['shippingPayment', 'payment'], true)) { - return $shopwareMethods; + return $paymentMeans; } - return ($this->serializer)( - ($this->enrichedPaymentMeanProvider)( - PaymentMeanCollection::createFromShopwareArray($shopwareMethods) - ) + $userData = Shopware()->Modules()->Admin()->sGetUserData(); + // Remove stored payments for guest checkout + if ((int)$userData['additional']['user']['accountmode'] === Customer::ACCOUNT_MODE_FAST_LOGIN + || !$this->isCreditCardEnabled()) { + $paymentMeans = $this->filterStoredPaymentMethods($paymentMeans); + } + + return $this->paymentMeansEnricher->enrich($paymentMeans); + } + + private function filterStoredPaymentMethods($paymentMeans): array + { + return array_filter(array_map(static function (array $paymentMean) { + if ($paymentMean['name'] === AdyenPayment::STORED_PAYMENT_UMBRELLA_NAME) { + return null; + } + + return $paymentMean; + }, $paymentMeans)); + } + + /** + * @return bool + * + * @throws Exception + */ + private function isCreditCardEnabled(): bool + { + /** @var PaymentMethodConfigRepository $repository */ + $repository = ServiceRegister::getService(PaymentMethodConfigRepository::class); + + $cardConfig = StoreContext::doWithStore( + '' . Shopware()->Shop()->getId(), + [$repository, 'getPaymentMethodByCode'], + [(string)PaymentMethodCode::scheme()] ); + + return $cardConfig !== null; } } diff --git a/Subscriber/EnrichUserAdditionalPaymentSubscriber.php b/Subscriber/EnrichUserAdditionalPaymentSubscriber.php new file mode 100755 index 00000000..9dd63785 --- /dev/null +++ b/Subscriber/EnrichUserAdditionalPaymentSubscriber.php @@ -0,0 +1,101 @@ +paymentMeansEnricher = $paymentMeansEnricher; + $this->session = $session; + } + + /** + * Run as early as possible but after @see AddStoredMethodUserPreferenceToView + * @return array[] + */ + public static function getSubscribedEvents(): array + { + return [ + 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => ['__invoke', -99988], + ]; + } + + public function __invoke(\Enlight_Controller_ActionEventArgs $args): void + { + $subject = $args->getSubject(); + if (!in_array($args->getRequest()->getActionName(), ['confirm', 'shippingPayment'])) { + return; + } + + $userData = $subject->View()->getAssign('sUserData'); + $paymentMeanId = $userData['additional']['payment']['id'] ?? null; + $this->session->offsetUnset('adyenEnrichedPaymentMean'); + + if (null === $paymentMeanId) { + return; + } + + $storedMethodId = (string)$this->session->get('adyenStoredPaymentMethodId'); + if (empty($storedMethodId)) { + $userPreferences = $subject->View()->getAssign('adyenUserPreference'); + $storedMethodId = $userPreferences['storedMethodId'] ?? ''; + } + + $enrichedPaymentMean = $this->paymentMeansEnricher->enrichPaymentMean( + $userData['additional']['payment'], + $storedMethodId + ); + + // Reset payment mean for guest checkout + if ( + !empty($storedMethodId) && + (int)$userData['additional']['user']['accountmode'] === Customer::ACCOUNT_MODE_FAST_LOGIN + ) { + $enrichedPaymentMean = []; + } + + // Keep enriched payment mean in session for services that do not have access to view (do not extend controllers) + $this->session->offsetSet('adyenEnrichedPaymentMean', $enrichedPaymentMean); + + + // Customer probably changed address to unsupported address for selected Adyen payment. Force payment selection. + if (empty($enrichedPaymentMean)) { + $subject->redirect(['controller' => 'checkout', 'action' => 'shippingPayment']); + + return; + } + + $userData['additional']['payment'] = $enrichedPaymentMean; + + // Make sure that redirection from Amazon with session id has all confirmations checked + if ($args->getRequest()->getParam('amazonCheckoutSessionId')) { + $args->getRequest()->setParam('sAGB', true); + $args->getRequest()->setParam('esdAgreementChecked', true); + $args->getRequest()->setParam('serviceAgreementChecked', true); + } + + $subject->View()->assign('sUserData', $userData); + $subject->View()->assign('sPayment', $userData['additional']['payment']); + } +} diff --git a/Subscriber/ExtendOrderDetailsHandler.php b/Subscriber/ExtendOrderDetailsHandler.php new file mode 100644 index 00000000..6229eec7 --- /dev/null +++ b/Subscriber/ExtendOrderDetailsHandler.php @@ -0,0 +1,45 @@ + 'onOrderPostDispatch']; + } + + /** + * Injects proper extjs files for order view extension. + * + * @param Enlight_Controller_ActionEventArgs $args + */ + public function onOrderPostDispatch(Enlight_Controller_ActionEventArgs $args) + { + /** @var Shopware_Controllers_Backend_Order $controller */ + $controller = $args->getSubject(); + + $view = $controller->View(); + $request = $controller->Request(); + + if ($view && $request->getActionName() === 'index') { + $view->extendsTemplate('backend/adyen_detail/app.js'); + } + + if ($view && $request->getActionName() === 'load') { + $view->extendsTemplate('backend/adyen_detail/store/transaction.js'); + $view->extendsTemplate('backend/adyen_detail/model/transaction.js'); + $view->extendsTemplate('backend/adyen_detail/view/window.js'); + $view->extendsTemplate('backend/adyen_list/adyen_order_list.js'); + $view->extendsTemplate('backend/adyen_list/models/adyen_order_model.js'); + $view->extendsTemplate('backend/adyen_detail/controller/adyen_order_details_controller.js'); + } + } +} diff --git a/Subscriber/FinishPageSubscriber.php b/Subscriber/FinishPageSubscriber.php new file mode 100644 index 00000000..bed03077 --- /dev/null +++ b/Subscriber/FinishPageSubscriber.php @@ -0,0 +1,35 @@ + '__invoke', + ]; + } + + public function __invoke(\Enlight_Controller_ActionEventArgs $args): void + { + $subject = $args->getSubject(); + if ($args->getRequest()->getActionName() === 'finish') { + $temporaryId = $args->getRequest()->get('sUniqueID'); + + $subject->View()->assign('merchantReference', $temporaryId); + + if (Shopware()->Session()->offsetExists('adyenAction')) { + $subject->View()->assign('adyenAction', Shopware()->Session()->offsetGet('adyenAction')); + Shopware()->Session()->offsetUnset('adyenAction'); + } + } + } +} diff --git a/Subscriber/LimitPercentageSurcharge.php b/Subscriber/LimitPercentageSurcharge.php new file mode 100755 index 00000000..97c75f67 --- /dev/null +++ b/Subscriber/LimitPercentageSurcharge.php @@ -0,0 +1,73 @@ +session = $session; + $this->connection = $connection; + } + + public static function getSubscribedEvents(): array + { + return [ + 'Shopware_Modules_Basket_BeforeAddOrderSurchargePercent' => '__invoke', + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + * + * @return void + * + * @throws Exception + */ + public function __invoke(Enlight_Event_EventArgs $args): void + { + $surchargeParams = $args->get('surcharge'); + if (empty($surchargeParams['price'])) { + return; + } + + $enrichedPaymentMean = $this->session->get('adyenEnrichedPaymentMean'); + if ( + empty($enrichedPaymentMean['isAdyenPaymentMethod']) || + empty($enrichedPaymentMean['surchargeLimit']) || + (float)$enrichedPaymentMean['surchargeLimit'] > (float)$surchargeParams['price'] + ) { + return; + } + + $surchargeParams['price'] = (float)$enrichedPaymentMean['surchargeLimit'] * $surchargeParams['currencyFactor']; + $surchargeParams['netprice'] = $surchargeParams['price'] / (1 + $surchargeParams['tax_rate'] / 100); + $this->connection->insert('s_order_basket', $surchargeParams); + + $args->setProcessed(true); + } +} diff --git a/Subscriber/Notification/LogIncomingNotification.php b/Subscriber/Notification/LogIncomingNotification.php deleted file mode 100755 index 3ac6b8e8..00000000 --- a/Subscriber/Notification/LogIncomingNotification.php +++ /dev/null @@ -1,43 +0,0 @@ -logger = $logger; - } - - public static function getSubscribedEvents(): array - { - return [ - Event::NOTIFICATION_RECEIVE => 'logNotifications', - ]; - } - - public function logNotifications(Enlight_Event_EventArgs $args): void - { - $items = $args->get('items'); - - foreach ($items as $item) { - $this->logger->debug('Incoming notification', ['json' => $item]); - } - } -} diff --git a/Subscriber/Notification/LogIncomingNotificationSubscriber.php b/Subscriber/Notification/LogIncomingNotificationSubscriber.php deleted file mode 100755 index 0b2715bf..00000000 --- a/Subscriber/Notification/LogIncomingNotificationSubscriber.php +++ /dev/null @@ -1,36 +0,0 @@ -logger = $logger; - } - - public static function getSubscribedEvents(): array - { - return [ - Event::NOTIFICATION_RECEIVE => '__invoke', - ]; - } - - public function __invoke(Enlight_Event_EventArgs $args): void - { - $items = $args->get('items') ?? []; - foreach ($items as $item) { - $this->logger->debug('Incoming notification', ['json' => $item]); - } - } -} diff --git a/Subscriber/Notification/UpdateOrderPsPSubscriber.php b/Subscriber/Notification/UpdateOrderPsPSubscriber.php deleted file mode 100755 index 25b40226..00000000 --- a/Subscriber/Notification/UpdateOrderPsPSubscriber.php +++ /dev/null @@ -1,47 +0,0 @@ -orderManager = $orderManager; - } - - public static function getSubscribedEvents(): array - { - return [ - Event::NOTIFICATION_PROCESS_AUTHORISATION => '__invoke', - ]; - } - - public function __invoke(Enlight_Event_EventArgs $args): void - { - /** - * @var \Shopware\Models\Order\Order $order - * @var \AdyenPayment\Models\Notification $notification - */ - $order = $args->get('order'); - $notification = $args->get('notification'); - if (!$notification->isSuccess()) { - return; - } - - if ($order->getTransactionId() === $notification->getPspReference()) { - return; - } - - $this->orderManager->updatePspReference($order, $notification->getPspReference()); - } -} diff --git a/Subscriber/OrderEmailSubscriber.php b/Subscriber/OrderEmailSubscriber.php deleted file mode 100644 index dd890d31..00000000 --- a/Subscriber/OrderEmailSubscriber.php +++ /dev/null @@ -1,134 +0,0 @@ -modelManager = $modelManager; - $this->paymentInfoRepository = $this->modelManager->getRepository(PaymentInfo::class); - $this->orderRepository = $this->modelManager->getRepository(Order::class); - $this->orderMailService = $orderMailService; - } - - /** - * @return string[] - * - * @psalm-return array{Shopware_Modules_Order_SendMail_Send: 'shouldStopEmailSending', Shopware_Modules_Order_SaveOrder_ProcessDetails: 'setPaymentInfoOrderNumber', Enlight_Controller_Action_PostDispatch_Frontend_Checkout: 'onCheckoutDispatch'} - */ - public static function getSubscribedEvents() - { - return [ - 'Shopware_Modules_Order_SendMail_Send' => 'shouldStopEmailSending', - 'Shopware_Modules_Order_SaveOrder_ProcessDetails' => 'setPaymentInfoOrderNumber', - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'onCheckoutDispatch', - ]; - } - - public function setPaymentInfoOrderNumber(Enlight_Event_EventArgs $args) - { - $orderId = $args->get('orderId'); - $paymentInfoId = Shopware()->Session()->get(AdyenPayment::SESSION_ADYEN_PAYMENT_INFO_ID); - - if (!empty($orderId)) { - $orderNumber = $this->getOrderNumber($orderId); - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $this->paymentInfoRepository->findOneBy([ - 'id' => $paymentInfoId, - ]); - - if ($paymentInfo) { - $paymentInfo->setOrdernumber($orderNumber); - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - } - - return $args->getReturn(); - } - - /** - * @return false|null - */ - public function shouldStopEmailSending(Enlight_Event_EventArgs $args) - { - $variables = $args->get('variables'); - $paymentMean = PaymentMean::createFromShopwareArray($variables['additional']['payment'] ?? []); - if ( - $this->orderMailService->isOrderConfirmationEmailRestricted() - && $paymentMean->getSource()->equals(SourceType::adyen()) - ) { - /** @var PaymentInfo $paymentInfo */ - $paymentInfo = $this->paymentInfoRepository->findOneBy([ - 'ordernumber' => $variables['ordernumber'], - ]); - - if ($paymentInfo && empty($paymentInfo->getOrdermailVariables())) { - if (($context = $args->get('context')) && array_key_exists('sCurrency', $context)) { - $variables['adyen_currency'] = $context['sCurrency']; - } - - $paymentInfo->setOrdermailVariables(json_encode($variables)); - - $this->modelManager->persist($paymentInfo); - $this->modelManager->flush($paymentInfo); - } - - return false; - } - } - - public function onCheckoutDispatch(Enlight_Event_EventArgs $args): void - { - /** @var Shopware_Controllers_Frontend_Checkout $subject */ - $subject = $args->getSubject(); - - if ('finish' !== $subject->Request()->getActionName()) { - return; - } - - $data = $subject->View()->getAssign(); - - if (!$data['sOrderNumber']) { - return; - } - - $this->orderMailService->sendOrderConfirmationMail(strval($data['sOrderNumber'])); - } - - private function getOrderNumber($orderId) - { - return $this->orderRepository->findOneBy([ - 'id' => $orderId, - ])->getNumber(); - } -} diff --git a/Subscriber/OrderListHandler.php b/Subscriber/OrderListHandler.php new file mode 100644 index 00000000..1b82f763 --- /dev/null +++ b/Subscriber/OrderListHandler.php @@ -0,0 +1,53 @@ + 'extendOrderList', + ]; + } + + public function extendOrderList(Enlight_Hook_HookArgs $args): void + { + $return = $args->getReturn(); + + foreach ($return['data'] as $index => $order) { + $log = $this->getLogService()->findByMerchantReference($order['temporaryId']); + + if (!$log) { + continue; + } + + $return['data'][$index]['adyenPspReference'] = $log->getPspReference(); + $return['data'][$index]['adyenPaymentMethod'] = $log->getPaymentMethod(); + } + + $args->setReturn($return); + } + + /** + * @return TransactionLogService + */ + private function getLogService(): TransactionLogService + { + if ($this->logService === null) { + $this->logService = ServiceRegister::getService(TransactionLogService::class); + } + + return $this->logService; + } +} diff --git a/Subscriber/ShopDeletedSubscriber.php b/Subscriber/ShopDeletedSubscriber.php new file mode 100644 index 00000000..bc947df2 --- /dev/null +++ b/Subscriber/ShopDeletedSubscriber.php @@ -0,0 +1,62 @@ + 'disconnectOnShopDeletion' + ]; + } + + /** + * @return void + */ + public function disconnectOnShopDeletion() + { + $params = Shopware()->Front()->Request()->getParams(); + + if ($params['_repositoryClass'] !== 'shop' || $params['id'] === 0) { + return; + } + + if (!in_array($params['id'], $this->getStoreService()->getConnectedStores())) { + return; + } + + try { + StoreContext::doWithStore( + $params['id'], + [$this->getDisconnectService(), 'disconnect'] + ); + } catch (Exception $e) { + Logger::logError('Substore deleted. Failed to disconnect substore because ' . $e->getMessage()); + } + } + + /** + * @return DisconnectService + */ + private function getDisconnectService(): DisconnectService + { + return ServiceRegister::getService(DisconnectService::class); + } + + /** + * @return StoreService + */ + private function getStoreService(): StoreService + { + return ServiceRegister::getService(StoreService::class); + } +} \ No newline at end of file diff --git a/Subscriber/TemplateRegistration.php b/Subscriber/TemplateRegistration.php new file mode 100644 index 00000000..ba86d661 --- /dev/null +++ b/Subscriber/TemplateRegistration.php @@ -0,0 +1,56 @@ +pluginDirectory = $pluginDirectory; + $this->templateManager = $templateManager; + $this->snippetManager = $snippetManager; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PreDispatch' => 'onPreDispatch' + ]; + } + + public function onPreDispatch() + { + $this->templateManager->addTemplateDir($this->pluginDirectory . '/Resources/views'); + $this->snippetManager->addConfigDir($this->pluginDirectory . '/Resources/snippets'); + } +} diff --git a/Subscriber/UpdateStoredPaymentMethodViewData.php b/Subscriber/UpdateStoredPaymentMethodViewData.php new file mode 100755 index 00000000..fba364af --- /dev/null +++ b/Subscriber/UpdateStoredPaymentMethodViewData.php @@ -0,0 +1,37 @@ + '__invoke']; + } + + public function __invoke(\Enlight_Controller_ActionEventArgs $args): void + { + $subject = $args->getSubject(); + $actionName = $args->getRequest()->getActionName(); + $isShippingPaymentView = 'shippingPayment' === $actionName && !$args->getRequest()->getParam('isXHR'); + if (!$isShippingPaymentView) { + return; + } + + $userData = $subject->View()->getAssign('sUserData'); + $storedMethodId = $userData['additional']['payment']['storedPaymentMethodId'] ?? null; + if (empty($storedMethodId)) { + return; + } + + // Make sure that form data has complete id in case when stored payment method is selected + $formData = $subject->View()->getAssign('sFormData'); + $formData['payment'] .= "_$storedMethodId"; + $subject->View()->assign('sFormData', $formData); + } +} diff --git a/Utilities/Plugin.php b/Utilities/Plugin.php new file mode 100644 index 00000000..c397ce9c --- /dev/null +++ b/Utilities/Plugin.php @@ -0,0 +1,61 @@ + 'frontend', + 'controller' => $controller, + 'action' => $action + ], $params); + + $url = Shopware()->Front()->Router()->assemble($params); + + return str_replace('http:', 'https:', $url); + } + + /** + * Get backend controller url. + * + * @param $controller + * @param $action + * @param array $params + * + * @return mixed|string + */ + public static function getBackendUrl($controller, $action, array $params = []) + { + $csrfToken = Shopware()->Container()->get('backendsession')->offsetGet('X-CSRF-Token'); + + $params = array_merge( + [ + 'module' => 'backend', + 'controller' => $controller, + 'action' => $action, + ], + $params, + ['__csrf_token' => $csrfToken] + ); + + return Shopware()->Front()->Router()->assemble($params); + } +} diff --git a/Utils/JsonUtil.php b/Utils/JsonUtil.php deleted file mode 100644 index a5c37652..00000000 --- a/Utils/JsonUtil.php +++ /dev/null @@ -1,61 +0,0 @@ - null, - 'cupsecureplus.smscode' => null, - 'facilitatorAccessToken' => null, - 'oneTimePasscode' => null, - 'orderID' => null, - 'payerID' => null, - 'payload' => null, - 'paymentID' => null, - 'paymentStatus' => null, - 'redirectResult' => null, - 'threeDSResult' => null, - 'threeds2.challengeResult' => null, - 'threeds2.fingerprint' => null, - ]; - - public static function forPaymentDetails(array $data): array - { - if (!$data) { - return []; - } - - return array_intersect_key($data, self::ALLOWED_PAYMENT_DETAIL_V67_KEYS); - } -} diff --git a/Utils/Sanitize.php b/Utils/Sanitize.php deleted file mode 100644 index 41064702..00000000 --- a/Utils/Sanitize.php +++ /dev/null @@ -1,18 +0,0 @@ -> /usr/local/etc/php/php.ini - - composer install --prefer-dist - - composer install --prefer-dist -d ./tools/ - - php ./vendor/bin/grumphp run - - branches: - master: - - step: - name: Code Compatibility - caches: - - composer - script: - - echo "memory_limit=-1" >> /usr/local/etc/php/php.ini - - composer install --prefer-dist - - composer install --prefer-dist -d ./tools/ - - php ./vendor/bin/grumphp run --testsuite="code-compatibility" diff --git a/composer.json b/composer.json index 6c02164b..5c69eca7 100644 --- a/composer.json +++ b/composer.json @@ -23,23 +23,30 @@ "autoload-dev": { "psr-4": { "AdyenPayment\\": "./", - "AdyenPayment\\Tests\\": "tests/" + "AdyenPayment\\Tests\\": "tests/", + "Adyen\\Core\\Tests\\Infrastructure\\": "vendor/adyen/integration-core/tests/Infrastructure", + "Adyen\\Core\\Tests\\BusinessLogic\\": "vendor/adyen/integration-core/tests/BusinessLogic" } }, "require": { "php": "^7.2|^7.4|^8.0", "ext-json": "*", "ext-zip": "*", - "adyen/php-api-library": "^10.0" - }, - "require-dev": { - "phpro/grumphp-shim": "^1.5" + "adyen/integration-core": "1.0.0", + "ext-simplexml": "*" }, "minimum-stability": "dev", "prefer-stable": true, - "config": { - "allow-plugins": { - "phpro/grumphp-shim": true + "repositories": [ + { + "type": "vcs", + "no-api": true, + "url": "git@github.com:logeecom/adyen-php-webhook-module.git" + }, + { + "type": "vcs", + "no-api": true, + "url": "git@github.com:logeecom/adyen-integration-core.git" } - } + ] } diff --git a/composer.lock b/composer.lock index 02b77faf..bb981b73 100644 --- a/composer.lock +++ b/composer.lock @@ -4,273 +4,97 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e50ffb80a77ed45f404f60301a058b10", + "content-hash": "dc1b8ee2ff63499fb373da3ef858d9e5", "packages": [ { - "name": "adyen/php-api-library", - "version": "10.1.0", + "name": "adyen/integration-core", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/Adyen/adyen-php-api-library.git", - "reference": "91917518e17595a91a3cc5887dd95e7d76767276" + "url": "git@github.com:logeecom/adyen-integration-core.git", + "reference": "41d8f36801829d6152fe52e585c6ad1b1853f85b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adyen/adyen-php-api-library/zipball/91917518e17595a91a3cc5887dd95e7d76767276", - "reference": "91917518e17595a91a3cc5887dd95e7d76767276", + "url": "https://api.github.com/repos/logeecom/adyen-integration-core/zipball/41d8f36801829d6152fe52e585c6ad1b1853f85b", + "reference": "41d8f36801829d6152fe52e585c6ad1b1853f85b", "shasum": "" }, "require": { + "adyen/php-webhook-module": "0.1.0", "ext-ctype": "*", - "ext-curl": "*", "ext-json": "*", - "ext-mbstring": "*", - "ext-openssl": "*", - "monolog/monolog": ">=1.16", - "php": ">=5.6" + "php": ">=7.2" }, "require-dev": { - "dms/phpunit-arraysubset-asserts": "0.2.1", - "friendsofphp/php-cs-fixer": "*", - "php-coveralls/php-coveralls": "2.4.3", - "phpunit/phpunit": "9.5.4", - "squizlabs/php_codesniffer": "3.6.0" + "phpunit/phpunit": "^8.5", + "symfony/console": "^4.4" }, "type": "library", "autoload": { "psr-4": { - "Adyen\\": "src/Adyen/" + "Adyen\\Core\\Infrastructure\\": "src/Infrastructure", + "Adyen\\Core\\BusinessLogic\\": "src/BusinessLogic" } }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "A PHP client library for accessing Adyen APIs", - "homepage": "https://github.com/Adyen/adyen-php-api-library", - "keywords": [ - "adyen" - ], - "support": { - "issues": "https://github.com/Adyen/adyen-php-api-library/issues", - "source": "https://github.com/Adyen/adyen-php-api-library/tree/10.1.0" - }, - "time": "2021-04-30T14:03:59+00:00" - }, - { - "name": "monolog/monolog", - "version": "2.9.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7 || ^8", - "ext-json": "*", - "graylog2/gelf-php": "^1.4.2 || ^2@dev", - "guzzlehttp/guzzle": "^7.4", - "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", - "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", - "symfony/mailer": "^5.4 || ^6", - "symfony/mime": "^5.4 || ^6" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", - "ext-mbstring": "Allow to work properly with unicode symbols", - "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", - "ext-openssl": "Required to send log messages using SSL", - "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.x-dev" - } - }, - "autoload": { + "autoload-dev": { "psr-4": { - "Monolog\\": "src/Monolog" + "Adyen\\Core\\Tests\\Infrastructure\\": "tests/Infrastructure", + "Adyen\\Core\\Tests\\BusinessLogic\\": "tests/BusinessLogic", + "Adyen\\Core\\Console\\": "console" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "https://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "support": { - "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.1" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } + "proprietary" ], - "time": "2023-02-06T13:44:46+00:00" + "description": "Core Adyen integration library", + "time": "2023-08-14T13:11:58+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "adyen/php-webhook-module", + "version": "0.1.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "git@github.com:logeecom/adyen-php-webhook-module.git", + "reference": "0ab11cbccbd0ee187fb6f4f643e4cf8747f938ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/logeecom/adyen-php-webhook-module/zipball/0ab11cbccbd0ee187fb6f4f643e4cf8747f938ac", + "reference": "0ab11cbccbd0ee187fb6f4f643e4cf8747f938ac", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } + "require-dev": { + "phpunit/phpunit": "^9.5.0", + "squizlabs/php_codesniffer": "^3.5" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Adyen\\Webhook\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - } - ], - "packages-dev": [ - { - "name": "phpro/grumphp-shim", - "version": "v1.13.0", - "source": { - "type": "git", - "url": "https://github.com/phpro/grumphp-shim.git", - "reference": "973a933d176be41f1196d8db7851e32f985dd798" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpro/grumphp-shim/zipball/973a933d176be41f1196d8db7851e32f985dd798", - "reference": "973a933d176be41f1196d8db7851e32f985dd798", - "shasum": "" - }, - "require": { - "composer-plugin-api": "~2.0", - "ext-json": "*", - "php": "^7.4 || ^8.0" - }, - "replace": { - "phpro/grumphp": "self.version" - }, - "require-dev": { - "humbug/box": "^3.16" - }, - "bin": [ - "grumphp", - "grumphp.phar" - ], - "type": "composer-plugin", - "extra": { - "class": "GrumPHP\\Composer\\GrumPHPPlugin" - }, - "autoload": { + "autoload-dev": { "psr-4": { - "GrumPHP\\": "src" + "Adyen\\Webhook\\Test\\": "tests/" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Toon Verwerft", - "email": "toon.verwerft@phpro.be" - }, - { - "name": "Community", - "homepage": "https://github.com/phpro/grumphp/graphs/contributors" + "name": "Adyen" } ], - "description": "GrumPHP Phar distribution", - "support": { - "issues": "https://github.com/phpro/grumphp-shim/issues", - "source": "https://github.com/phpro/grumphp-shim/tree/v1.13.0" - }, - "time": "2022-06-24T08:34:50+00:00" + "description": "Webhook module for Adyen Payment Integrations", + "time": "2023-01-23T12:47:14+00:00" } ], + "packages-dev": [], "aliases": [], "minimum-stability": "dev", "stability-flags": [], @@ -279,7 +103,8 @@ "platform": { "php": "^7.2|^7.4|^8.0", "ext-json": "*", - "ext-zip": "*" + "ext-zip": "*", + "ext-simplexml": "*" }, "platform-dev": [], "plugin-api-version": "2.1.0" diff --git a/grumphp.yml.dist b/grumphp.yml.dist deleted file mode 100644 index 537b226c..00000000 --- a/grumphp.yml.dist +++ /dev/null @@ -1,63 +0,0 @@ -parameters: - grumphp.ignore_patterns: - - '*/vendor' - - '.idea' - - '.gitlab-ci' - - '.github/' - - 'tests/' - - 'tools/' - -grumphp: - environment: - paths: - - 'tools/vendor/bin' - tasks: - git_commit_message: - matchers: - - /^(ASW-[0-9]*)/ - multiline: false - max_subject_width: 80 - git_blacklist: - keywords: - - '__debug(' - - 'console.log(' - - 'debug_backtrace(' - - 'die(' - - 'dump(' - - 'exit;' - - "\\bdd\\b(" - triggered_by: [php,js,tpl] - file_size: - max_size: 5M - composer: ~ - phplint: - triggered_by: ['php'] - xmllint: - triggered_by: ['xml', 'xml.dist'] - yamllint: - parse_custom_tags: true - phpcsfixer: - config: '.php-cs-fixer.dist.php' - phpparser: - ignore_patterns: '%grumphp.ignore_patterns%' - visitors: - no_exit_statements: ~ - never_use_else: ~ - forbidden_function_calls: - blacklist: [ echo, print, print_r, phpinfo ] - psalm: - config: psalm.xml.dist - ignore_patterns: '%grumphp.ignore_patterns%' - phpunit: - always_execute: true - php_compatibility: - scripts: - - ["-c", "php ./tools/vendor/bin/phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 7.4- --extensions=php --ignore=*/vendor/*,*/tests/*"] - metadata: - task: shell - - testsuites: - code-compatibility: - tasks: ['composer', 'php_compatibility'] - slim: - tasks: ['file_size', 'composer', 'phplint', 'xmllint', 'yamllint', 'phpcsfixer', 'phpparser', 'php_compatibility'] diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index dfbda88e..00000000 --- a/phpcs.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - vendor/* - . - - *.js - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c8d5635d..de9033e1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,18 +1,13 @@ - - - - - - - - tests/Unit - - - tests/Integration - - + + + + tests + + + + + ./tests + + + diff --git a/plugin.xml b/plugin.xml index beec4469..43b9fe02 100644 --- a/plugin.xml +++ b/plugin.xml @@ -4,7 +4,7 @@ - 3.9.9 + 4.0.0 Adyen Adyen https://adyen.com @@ -443,70 +443,30 @@ * Optimize: Handling of the invalid Webhook requests - - - * Optimize: Add compatibility with Online Banking PL payment method - * Optimize: Handling of redirect payment request from Adyen - - - * Optimize: Add compatibility with Online Banking PL payment method - * Optimize: Handling of redirect payment request from Adyen - - - - - * Optimize: Remove unnecessary third-party dependencies from plugin - - - * Optimize: Remove unnecessary third-party dependencies from plugin - - - - - * Optimize: Handling empty order lines for Klarna payment requests - - - * Optimize: Handling empty order lines for Klarna payment requests - - - - - * Fix: Fix issue with shipping method selection when stored payment method is selected. - - - * Fix: Fix issue with shipping method selection when stored payment method is selected. - - - - - * Fix: Add support for Apple Pay payment method. - - - * Fix: Add support for Apple Pay payment method. - - - - - * Fix: Fix full refund not working due to wrong PSP reference being sent in refund request. - - - * Fix: Fix full refund not working due to wrong PSP reference being sent in refund request. - - - - - * Fix: Add support for ESD items - - - * Fix: Add support for ESD items - - - - - * Fix: Fix bug when payment method is switched on payment selection page. - - - * Fix: Fix bug when payment method is switched on payment selection page. + + + * New admin user interface and experience - Navigate effortlessly through an enhanced interface designed for simplicity and seamless plugin configuration. + * Expanded payment method options - Introducing new supported payment methods, giving customers more choices and convenience during checkout. + * Payment methods as a full express option - Offer customers more options with new supported payment methods, including the checkout express lineup: Apple Pay, Google Pay, Amazon Pay, and Paypal. + * Revamped checkout - Benefit from the latest version of Adyen's Checkout API, but also with the new implementation of the checkout and payment process that meets Shopware 5 best practices. + * Partial refunds - Easily process partial refunds, providing greater flexibility in handling customer returns. + * Partial capture - Seamlessly capture funds partially, granting you flexibility in managing complex orders. + * Capture control - Choose between manual and auto-capture of funds, tailoring the payment process to your business needs. + * Advanced data sending - Send L2 and L3 data for enhanced transaction insights, facilitating smoother business operations. + * Risk score visibility - View risk scores in order payment details, assisting in informed decision-making and fraud prevention. + * Adyen Giving - Enable charitable donations through Adyen Giving, allowing your customers to contribute effortlessly during transactions. + + + * New admin user interface and experience - Navigate effortlessly through an enhanced interface designed for simplicity and seamless plugin configuration. + * Expanded payment method options - Introducing new supported payment methods, giving customers more choices and convenience during checkout. + * Payment methods as a full express option - Offer customers more options with new supported payment methods, including the checkout express lineup: Apple Pay, Google Pay, Amazon Pay, and Paypal. + * Revamped checkout - Benefit from the latest version of Adyen's Checkout API, but also with the new implementation of the checkout and payment process that meets Shopware 5 best practices. + * Partial refunds - Easily process partial refunds, providing greater flexibility in handling customer returns. + * Partial capture - Seamlessly capture funds partially, granting you flexibility in managing complex orders. + * Capture control - Choose between manual and auto-capture of funds, tailoring the payment process to your business needs. + * Advanced data sending - Send L2 and L3 data for enhanced transaction insights, facilitating smoother business operations. + * Risk score visibility - View risk scores in order payment details, assisting in informed decision-making and fraud prevention. + * Adyen Giving - Enable charitable donations through Adyen Giving, allowing your customers to contribute effortlessly during transactions. diff --git a/psalm.xml.dist b/psalm.xml.dist deleted file mode 100644 index 6691d01f..00000000 --- a/psalm.xml.dist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/storage/apple-developer-merchantid-domain-association.archive b/storage/apple-developer-merchantid-domain-association.archive deleted file mode 100644 index 852d5383..00000000 Binary files a/storage/apple-developer-merchantid-domain-association.archive and /dev/null differ diff --git a/tests/BaseRepositoryWrapperTest.php b/tests/BaseRepositoryWrapperTest.php new file mode 100644 index 00000000..f4dee59f --- /dev/null +++ b/tests/BaseRepositoryWrapperTest.php @@ -0,0 +1,259 @@ + [], + ]; + /** + * @var BaseRepositoryTestAdapter + */ + protected $baseTest; + + /** + * BaseRepositoryWrapperTest constructor. + * + * @throws \Exception + */ + public function __construct() + { + parent::__construct(...func_get_args()); + $this->baseTest = new BaseRepositoryTestAdapter(...func_get_args()); + $entityManager = Shopware()->Container()->get('models'); + $this->baseTest->setEntityManager($entityManager); + } + + /** + * Proxies method to base test. + * + * @param $name + * @param $arguments + */ + public function __call($name, $arguments) + { + if (method_exists($this->baseTest, $name) && is_callable([$this->baseTest, $name])) { + $this->baseTest->$name(...$arguments); + } + } + + /** + * @throws RepositoryNotRegisteredException + */ + public function testRegisteredRepositories(): void + { + $this->baseTest->testRegisteredRepositories(); + } + + /** + * @throws RepositoryNotRegisteredException + */ + public function testStudentMassInsert(): void + { + $this->baseTest->testStudentMassInsert(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testStudentUpdate(): void + { + $this->baseTest->testStudentUpdate(); + } + + /** + * @throws RepositoryNotRegisteredException + */ + public function testQueryAllStudents(): void + { + $this->baseTest->testQueryAllStudents(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersString(): void + { + $this->baseTest->testQueryWithFiltersString(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersInt(): void + { + $this->baseTest->testQueryWithFiltersInt(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithOr(): void + { + $this->baseTest->testQueryWithOr(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithAndAndOr(): void + { + $this->baseTest->testQueryWithAndAndOr(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithNotEquals(): void + { + $this->baseTest->testQueryWithNotEquals(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithGreaterThan(): void + { + $this->baseTest->testQueryWithGreaterThan(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithLessThan(): void + { + $this->baseTest->testQueryWithLessThan(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithGreaterEqualThan(): void + { + $this->baseTest->testQueryWithGreaterEqualThan(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithLessOrEqualThan(): void + { + $this->baseTest->testQueryWithLessOrEqualThan(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithCombinedComparisonOperators(): void + { + $this->baseTest->testQueryWithCombinedComparisonOperators(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithInOperator(): void + { + $this->baseTest->testQueryWithInOperator(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithNotInOperator(): void + { + $this->baseTest->testQueryWithNotInOperator(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithLikeOperator(): void + { + $this->baseTest->testQueryWithLikeOperator(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersAndSort(): void + { + $this->baseTest->testQueryWithFiltersAndSort(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithUnknownFieldSort(): void + { + $this->baseTest->testQueryWithUnknownFieldSort(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithUnIndexedFieldSort(): void + { + $this->baseTest->testQueryWithUnIndexedFieldSort(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithIdFieldSort(): void + { + $this->baseTest->testQueryWithIdFieldSort(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersAndLimit(): void + { + $this->baseTest->testQueryWithFiltersAndLimit(); + } + + /** + * @inheritDoc + * + * @throws RepositoryClassException + */ + public function setUp(): void + { + $this->baseTest->setUp(); + } + + /** + * @inheritDoc + */ + public function tearDown(): void + { + $this->baseTest->tearDown(); + } +} diff --git a/tests/Integration/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php b/tests/Integration/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php deleted file mode 100644 index ce2fa288..00000000 --- a/tests/Integration/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php +++ /dev/null @@ -1,15 +0,0 @@ -markTestIncomplete(); - } -} diff --git a/tests/PluginTest.php b/tests/PluginTest.php new file mode 100644 index 00000000..faf29e42 --- /dev/null +++ b/tests/PluginTest.php @@ -0,0 +1,21 @@ + [] + ]; + + public function testCanCreateInstance() + { + /** @var Plugin $plugin */ + $plugin = Shopware()->Container()->get('kernel')->getPlugins()['AdyenPayment']; + + $this->assertInstanceOf(Plugin::class, $plugin); + } +} diff --git a/tests/QueueItemRepositoryWrapperTest.php b/tests/QueueItemRepositoryWrapperTest.php new file mode 100644 index 00000000..61234261 --- /dev/null +++ b/tests/QueueItemRepositoryWrapperTest.php @@ -0,0 +1,192 @@ + [] + ]; + /** + * @var BaseQueueItemRepositoryTestAdapter + */ + protected $baseTest; + + /** + * QueueItemRepositoryWrapperTest constructor. + * + * @throws \Exception + */ + public function __construct() + { + parent::__construct(...func_get_args()); + $this->baseTest = new BaseQueueItemRepositoryTestAdapter(...func_get_args()); + $entityManager = Shopware()->Container()->get('models'); + $this->baseTest->setEntityManager($entityManager); + } + + /** + * Proxies method to base test. + * + * @param $name + * @param $arguments + */ + public function __call($name, $arguments) + { + if (is_callable([$this->baseTest, $name])) { + $this->baseTest->$name(...$arguments); + } + } + + /** + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testRegisteredRepositories(): void + { + $this->baseTest->testRegisteredRepositories(); + } + + /** + * @depends testRegisteredRepositories + * + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueueItemMassInsert(): void + { + $this->baseTest->testQueueItemMassInsert(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testUpdate(): void + { + $this->baseTest->testQueueItemMassInsert(); + + $this->baseTest->testUpdate(); + } + + /** + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueryAllQueueItems(): void + { + $this->baseTest->testQueryAllQueueItems(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersString(): void + { + $this->baseTest->testQueryWithFiltersString(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersInt(): void + { + $this->baseTest->testQueryWithFiltersInt(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersAndSort(): void + { + $this->baseTest->testQueryWithFiltersAndSort(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testQueryWithFiltersAndLimit(): void + { + $this->baseTest->testQueryWithFiltersAndLimit(); + } + + /** + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testFindOldestQueuedItems(): void + { + $this->baseTest->testFindOldestQueuedItems(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + * @throws QueueItemSaveException + */ + public function testSaveWithCondition(): void + { + $this->expectException(QueueItemSaveException::class); + $this->baseTest->testSaveWithCondition(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + * @throws QueueItemSaveException + */ + public function testSaveWithConditionWithNull(): void + { + $this->expectException(QueueItemSaveException::class); + $this->baseTest->testSaveWithConditionWithNull(); + } + + /** + * @throws QueryFilterInvalidParamException + * @throws RepositoryClassException + * @throws RepositoryNotRegisteredException + */ + public function testInvalidQueryFilter(): void + { + $this->expectException(QueryFilterInvalidParamException::class); + $this->baseTest->testInvalidQueryFilter(); + } + + /** + * @inheritDoc + * + * @throws RepositoryClassException + * @throws TaskRunnerStatusStorageUnavailableException + */ + public function setUp(): void + { + $this->baseTest->setUp(); + } + + /** + * @inheritDoc + */ + public function tearDown(): void + { + $this->baseTest->tearDown(); + } +} diff --git a/tests/TestComponents/BaseQueueItemRepositoryTestAdapter.php b/tests/TestComponents/BaseQueueItemRepositoryTestAdapter.php new file mode 100644 index 00000000..ad567fa2 --- /dev/null +++ b/tests/TestComponents/BaseQueueItemRepositoryTestAdapter.php @@ -0,0 +1,75 @@ +entityManager = $entityManager; + } + + /** + * @inheritDoc + * @throws TaskRunnerStatusStorageUnavailableException + */ + public function setUp(): void + { + $database = new TestDatabase($this->entityManager); + $database->install(); + + Bootstrap::init(); + + parent::setUp(); + } + + /** + * @inheritDoc + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * @return string + */ + public function getQueueItemEntityRepositoryClass(): string + { + return TestQueueItemRepository::getClassName(); + } + + /** + * Cleans up all storage services used by repositories + * @throws RepositoryNotRegisteredException + * @throws MappingException + */ + public function cleanUpStorage(): void + { + $database = new TestDatabase($this->entityManager); + $database->uninstall(); + $this->entityManager->clear(); + } +} diff --git a/tests/TestComponents/BaseRepositoryTestAdapter.php b/tests/TestComponents/BaseRepositoryTestAdapter.php new file mode 100644 index 00000000..333da851 --- /dev/null +++ b/tests/TestComponents/BaseRepositoryTestAdapter.php @@ -0,0 +1,66 @@ +entityManager = $entityManager; + } + + /** + * @inheritDoc + */ + public function setUp(): void + { + $database = new TestDatabase($this->entityManager); + $database->install(); + + Bootstrap::init(); + + parent::setUp(); + } + + /** + * @inheritDoc + */ + public function tearDown(): void + { + parent::tearDown(); + } + + /** + * @inheritDoc + */ + public function getStudentEntityRepositoryClass(): string + { + return TestBaseRepository::class; + } + + /** + * @inheritDoc + */ + public function cleanUpStorage(): void + { + $database = new TestDatabase($this->entityManager); + $database->uninstall(); + $this->entityManager->clear(); + } +} diff --git a/tests/TestComponents/Components/TestBaseRepository.php b/tests/TestComponents/Components/TestBaseRepository.php new file mode 100644 index 00000000..94052f31 --- /dev/null +++ b/tests/TestComponents/Components/TestBaseRepository.php @@ -0,0 +1,10 @@ +entityManager = $entityManager; + $this->schemaTool = new SchemaTool($this->entityManager); + } + public function install(): void + { + $this->schemaTool->updateSchema($this->getClassesMetaData(), true); + } + + public function uninstall(): void + { + $this->schemaTool->dropSchema($this->getClassesMetaData()); + } + + protected function getClassesMetaData(): array + { + return [ + $this->entityManager->getClassMetadata(TestEntity::class) + ]; + } +} diff --git a/tests/TestComponents/Components/TestEntity.php b/tests/TestComponents/Components/TestEntity.php new file mode 100644 index 00000000..eac686a4 --- /dev/null +++ b/tests/TestComponents/Components/TestEntity.php @@ -0,0 +1,20 @@ +configuration = $this->prophesize(ConfigurationInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->clientFactory = new ClientFactory($this->configuration->reveal(), $this->logger->reveal()); - } - - /** @test */ - public function it_is_a_client_factory(): void - { - $this->assertInstanceOf(ClientFactory::class, $this->clientFactory); - } - - /** @test */ - public function it_can_provide_a_client_for_test_environment(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn('shop-id'); - - $this->configuration->getMerchantAccount($shop)->willReturn($merchantAccount = 'mock-merchantAccount'); - $this->configuration->getApiKey($shop)->willReturn($apiKey = 'mock-apiKey'); - $this->configuration->getEnvironment($shop)->willReturn($environment = Environment::TEST); - $this->configuration->getApiUrlPrefix($shop)->willReturn('api-url-prefix'); - - $result = $this->clientFactory->provide($shop->reveal()); - - $this->assertInstanceOf(Client::class, $result); - $this->assertEquals($merchantAccount, $result->getConfig()->getMerchantAccount()); - $this->assertEquals($apiKey, $result->getConfig()->getXApiKey()); - $this->assertEquals($environment, $result->getConfig()->getEnvironment()); - $this->assertEquals(Client::ENDPOINT_TEST, $result->getConfig()->get('endpoint')); - $this->assertEquals($this->logger->reveal(), $result->getLogger()); - } - - /** @test */ - public function it_can_provide_a_client_for_live_environment(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn('shop-id'); - - $this->configuration->getMerchantAccount($shop)->willReturn($merchantAccount = 'mock-merchantAccount'); - $this->configuration->getApiKey($shop)->willReturn($apiKey = 'mock-apiKey'); - $this->configuration->getEnvironment($shop)->willReturn($environment = Environment::LIVE); - $this->configuration->getApiUrlPrefix($shop)->willReturn($urlPrefix = 'api-url-prefix'); - - $result = $this->clientFactory->provide($shop->reveal()); - - $this->assertInstanceOf(Client::class, $result); - $this->assertEquals($merchantAccount, $result->getConfig()->getMerchantAccount()); - $this->assertEquals($apiKey, $result->getConfig()->getXApiKey()); - $this->assertEquals($environment, $result->getConfig()->getEnvironment()); - $expectedEndpoint = Client::ENDPOINT_PROTOCOL.$urlPrefix.Client::ENDPOINT_LIVE_SUFFIX; - $this->assertEquals($expectedEndpoint, $result->getConfig()->get('endpoint')); - $this->assertEquals($this->logger->reveal(), $result->getLogger()); - } -} diff --git a/tests/Unit/AdyenApi/HttpClient/ClientMemoiseTest.php b/tests/Unit/AdyenApi/HttpClient/ClientMemoiseTest.php deleted file mode 100755 index 8864a30d..00000000 --- a/tests/Unit/AdyenApi/HttpClient/ClientMemoiseTest.php +++ /dev/null @@ -1,63 +0,0 @@ -clientFactory = $this->prophesize(ClientFactoryInterface::class); - $this->clientMemoise = new ClientMemoise($this->clientFactory->reveal()); - } - - /** @test */ - public function it_is_a_client_memoise(): void - { - $this->assertInstanceOf(ClientMemoise::class, $this->clientMemoise); - } - - /** @test */ - public function it_can_lookup_a_client(): void - { - $shop = new Shop(); - $client = $this->prophesize(Client::class); - $this->clientFactory->provide($shop)->willReturn($client); - - $result = $this->clientMemoise->lookup($shop); - - $this->assertSame($client->reveal(), $result); - } - - /** @test */ - public function it_can_return_a_memoised_client(): void - { - $shop = new Shop(); - $client = $this->prophesize(Client::class); - - $this->clientFactory->provide($shop)->shouldBeCalledOnce()->willReturn($client); - - $firstResult = $this->clientMemoise->lookup($shop); - $result = $this->clientMemoise->lookup($shop); - - $this->assertSame($client->reveal(), $firstResult); - $this->assertSame($client->reveal(), $result); - } -} diff --git a/tests/Unit/AdyenApi/HttpClient/ConfigValidatorTest.php b/tests/Unit/AdyenApi/HttpClient/ConfigValidatorTest.php deleted file mode 100644 index 5c1885e1..00000000 --- a/tests/Unit/AdyenApi/HttpClient/ConfigValidatorTest.php +++ /dev/null @@ -1,159 +0,0 @@ -adyenApiFactory = $this->prophesize(ClientFactoryInterface::class); - $this->configuration = $this->prophesize(ConfigurationInterface::class); - $this->shopRepository = $this->prophesize(EntityRepository::class); - - $this->configValidator = new ConfigValidator( - $this->adyenApiFactory->reveal(), - $this->configuration->reveal(), - $this->shopRepository->reveal() - ); - } - - /** @test */ - public function it_is_a_config_validator(): void - { - $this->assertInstanceOf(ConfigValidator::class, $this->configValidator); - } - - /** @test */ - public function it_will_return_a_violation_if_shop_is_not_found(): void - { - $this->shopRepository->find($shopId = 123456)->willReturn(null); - - $result = $this->configValidator->validate($shopId); - - $this->assertInstanceOf(ConstraintViolationList::class, $result); - $this->assertCount(1, $result); - $this->assertEquals(ConstraintViolationFactory::create('Shop not found for ID "'.$shopId.'".'), $result->get(0)); - } - - /** @test */ - public function it_will_return_a_violation_if_the_api_key_was_not_configured(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn($shopId = 123456); - $this->shopRepository->find($shopId)->willReturn($shop->reveal()); - - $this->configuration->getApiKey($shop)->willReturn(''); - $this->configuration->getMerchantAccount($shop->reveal())->willReturn('merchantAccount'); - - $result = $this->configValidator->validate($shopId); - - $this->assertInstanceOf(ConstraintViolationList::class, $result); - $this->assertCount(1, $result); - $this->assertEquals(ConstraintViolationFactory::create('Missing configuration: API key.'), $result->get(0)); - } - - /** @test */ - public function it_will_return_a_violation_if_the_merchant_account_was_not_configured(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn($shopId = 123456); - $this->shopRepository->find($shopId)->willReturn($shop->reveal()); - - $this->configuration->getApiKey($shop->reveal())->willReturn('api-key'); - $this->configuration->getMerchantAccount($shop->reveal())->willReturn(''); - - $this->assertEquals( - new ConstraintViolationList([ - ConstraintViolationFactory::create('Missing configuration: merchant account.'), - ]), - $this->configValidator->validate($shopId) - ); - } - - /** @test */ - public function it_will_return_a_violation_on_api_adyen_exception(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn($shopId = 123456); - $this->shopRepository->find($shopId)->willReturn($shop->reveal()); - - $this->configuration->getApiKey($shop)->willReturn('api-key'); - $this->configuration->getMerchantAccount($shop)->willReturn('merchantAccount'); - $this->adyenApiFactory->provide($shop)->willThrow(AdyenException::class); - - $this->assertEquals( - new ConstraintViolationList([ - ConstraintViolationFactory::create('Adyen API failed, check error logs'), - ]), - $this->configValidator->validate($shopId) - ); - } - - /** @test */ - public function it_can_validate_a_config(): void - { - $shop = $this->prophesize(Shop::class); - $shop->getId()->willReturn($shopId = 123456); - - $this->configuration->getApiKey($shop->reveal())->willReturn('api-key'); - $this->configuration->getMerchantAccount($shop->reveal())->willReturn($merchantAccount = 'merchantAccount'); - - $client = $this->createClientMock(); - $this->shopRepository->find($shopId)->willReturn($shop->reveal()); - - $this->adyenApiFactory->provide($shop->reveal())->willReturn($client->reveal()); - $this->configuration->getMerchantAccount($shop->reveal())->willReturn($merchantAccount); - - $this->assertEquals(new ConstraintViolationList(), $this->configValidator->validate($shopId)); - } - - private function createClientMock(): ObjectProphecy - { - $config = $this->prophesize(Config::class); - $config->get(Argument::any())->willReturn(Environment::TEST); - $config->getInputType(Argument::any())->willReturn(''); - $httpClient = $this->prophesize(ClientInterface::class); - $httpClient->requestJson(Argument::cetera())->willReturn([]); - - $client = $this->prophesize(Client::class); - $client->getConfig()->willReturn($config->reveal()); - $client->getHttpClient()->willReturn($httpClient->reveal()); - $client->getApiCheckoutVersion()->willReturn(''); - $client->getApiRecurringVersion()->willReturn(''); - - return $client; - } -} diff --git a/tests/Unit/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php b/tests/Unit/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php deleted file mode 100644 index d7459831..00000000 --- a/tests/Unit/AdyenApi/Recurring/DisableTokenRequestHandlerTest.php +++ /dev/null @@ -1,105 +0,0 @@ -customerNumberProvider = $this->prophesize(CustomerNumberProviderInterface::class); - $this->transportFactory = $this->prophesize(TransportFactoryInterface::class); - - $this->disableTokenRequestHandler = new DisableTokenRequestHandler( - $this->transportFactory->reveal(), - $this->customerNumberProvider->reveal() - ); - } - - /** @test */ - public function it_is_a_disable_token_request_handler(): void - { - $this->assertInstanceOf(DisableTokenRequestHandlerInterface::class, $this->disableTokenRequestHandler); - } - - /** @test */ - public function it_will_return_a_400_on_missing_customer_number(): void - { - $shop = $this->prophesize(Shop::class); - $this->customerNumberProvider->__invoke()->willReturn(''); - $this->transportFactory->recurring(Argument::any())->shouldNotBeCalled(); - - $result = $this->disableTokenRequestHandler->disableToken('recurringTokenId', $shop->reveal()); - - $this->assertEquals(ApiResponse::empty(), $result); - } - - /** @test */ - public function it_will_return_an_api_response_for_disable_token_success(): void - { - $shop = $this->prophesize(Shop::class); - $recurringTransport = $this->prophesize(Recurring::class); - $payload = [ - 'shopperReference' => $customerNumber = 'customer-number', - 'recurringDetailReference' => $recurringTokenId = 'recurringTokenId', - ]; - $this->customerNumberProvider->__invoke()->willReturn($customerNumber); - $this->transportFactory->recurring($shop)->willReturn($recurringTransport); - $recurringTransport->disable($payload)->willReturn([ - 'response' => 'successfully-disabled', - 'message' => $message = 'successfully-disabled', - ]); - - $result = $this->disableTokenRequestHandler->disableToken($recurringTokenId, $shop->reveal()); - - $this->assertInstanceOf(ApiResponse::class, $result); - $this->assertTrue($result->isSuccess()); - $this->assertEquals($message, $result->message()); - } - - /** @test */ - public function it_will_return_an_api_response_for_disable_token_error(): void - { - $shop = $this->prophesize(Shop::class); - $recurringTransport = $this->prophesize(Recurring::class); - $payload = [ - 'shopperReference' => $customerNumber = 'customer-number', - 'recurringDetailReference' => $recurringTokenId = 'recurringTokenId', - ]; - $this->customerNumberProvider->__invoke()->willReturn($customerNumber); - $this->transportFactory->recurring($shop)->willReturn($recurringTransport); - $recurringTransport->disable($payload)->willReturn([ - 'message' => $message = 'PaymentDetail not found', - ]); - - $result = $this->disableTokenRequestHandler->disableToken($recurringTokenId, $shop->reveal()); - - $this->assertInstanceOf(ApiResponse::class, $result); - $this->assertFalse($result->isSuccess()); - $this->assertEquals($message, $result->message()); - } -} diff --git a/tests/Unit/AdyenApi/TransportFactoryTest.php b/tests/Unit/AdyenApi/TransportFactoryTest.php deleted file mode 100755 index f8a5f7a3..00000000 --- a/tests/Unit/AdyenApi/TransportFactoryTest.php +++ /dev/null @@ -1,86 +0,0 @@ -clientFactory = $this->prophesize(ClientFactoryInterface::class); - $this->transportFactory = new TransportFactory($this->clientFactory->reveal()); - } - - /** @test */ - public function it_is_a_transport_factory(): void - { - $this->assertInstanceOf(TransportFactoryInterface::class, $this->transportFactory); - } - - /** @test */ - public function it_can_provide_a_recurring_transport(): void - { - $shop = new Shop(); - $adyenClient = $this->createClientMock(); - - $this->clientFactory->provide($shop)->willReturn($adyenClient); - - $result = $this->transportFactory->recurring($shop); - - $this->assertInstanceOf(Recurring::class, $result); - } - - /** @test */ - public function it_can_provide_a_checkout_transport(): void - { - $shop = new Shop(); - $adyenClient = $this->createClientMock(); - - $this->clientFactory->provide($shop)->willReturn($adyenClient); - - $result = $this->transportFactory->checkout($shop); - - $this->assertInstanceOf(Checkout::class, $result); - } - - private function createClientMock(): ObjectProphecy - { - $config = $this->prophesize(Config::class); - $config->get(Argument::any())->willReturn(Environment::TEST); - $config->getInputType(Argument::any())->willReturn(''); - $httpClient = $this->prophesize(ClientInterface::class); - $httpClient->requestJson(Argument::cetera())->willReturn([]); - - $client = $this->prophesize(Client::class); - $client->getConfig()->willReturn($config->reveal()); - $client->getHttpClient()->willReturn($httpClient->reveal()); - $client->getApiCheckoutVersion()->willReturn(''); - $client->getApiRecurringVersion()->willReturn(''); - - return $client; - } -} diff --git a/tests/Unit/Applepay/Exception/FileNotWrittenExceptionTest.php b/tests/Unit/Applepay/Exception/FileNotWrittenExceptionTest.php deleted file mode 100644 index 9a4638da..00000000 --- a/tests/Unit/Applepay/Exception/FileNotWrittenExceptionTest.php +++ /dev/null @@ -1,33 +0,0 @@ -exception = new FileNotWrittenException(); - } - - /** @test */ - public function it_is_a_runtime_exception(): void - { - $this->assertInstanceOf(\RuntimeException::class, $this->exception); - } - - /** @test */ - public function it_can_be_constructed_with_file_path(): void - { - $exception = $this->exception::withFilepath($filepath = 'path/to/file'); - - $this->assertInstanceOf(FileNotWrittenException::class, $exception); - $this->assertNotSame($this->exception, $exception); - $this->assertEquals('Could not write apple pay association file, path: "'.$filepath.'"', - $exception->getMessage()); - } -} diff --git a/tests/Unit/Collection/Payment/PaymentMeanCollectionTest.php b/tests/Unit/Collection/Payment/PaymentMeanCollectionTest.php deleted file mode 100644 index 22f9c8ce..00000000 --- a/tests/Unit/Collection/Payment/PaymentMeanCollectionTest.php +++ /dev/null @@ -1,186 +0,0 @@ -collection = new PaymentMeanCollection(); - } - - /** @test */ - public function it_implements_iterable(): void - { - self::assertInstanceOf(\IteratorAggregate::class, $this->collection); - } - - /** @test */ - public function it_can_count(): void - { - self::assertInstanceOf(\Countable::class, $this->collection); - self::assertCount(0, $this->collection); - } - - /** @test */ - public function it_can_map_with_a_callback(): void - { - $filteredSource = SourceType::adyen(); - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['source' => $filteredSource->getType()], - ]); - - $result = $collection->map(static function(PaymentMean $payment) { - return ['mapped']; - }); - - self::assertEquals([['mapped']], $result); - } - - /** @test */ - public function it_can_filter_by_source(): void - { - $filteredSource = SourceType::adyen(); - - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['id' => $expected = 123, 'source' => $filteredSource->getType()], - ['id' => 456, 'source' => '1'], - ]); - - $result = $collection->filterBySource($filteredSource); - - self::assertInstanceOf(PaymentMeanCollection::class, $result); - self::assertCount(1, $result); - self::assertEquals($expected, iterator_to_array($result)[0]->getId()); - } - - /** @test */ - public function it_can_exclude_adyen(): void - { - $filteredSource = SourceType::adyen(); - $expected = PaymentMeanCollection::createFromShopwareArray([ - ['source' => '1'], - ]); - - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['source' => $filteredSource->getType()], - ['source' => '1'], - ]); - - $result = $collection->filterExcludeAdyen(); - - self::assertEquals($expected, $result); - } - - /** @test */ - public function it_can_exclude_hidden(): void - { - $filteredSource = SourceType::adyen(); - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['id' => 123, 'source' => $filteredSource->getType(), 'hide' => true], - ['id' => $expected = 345, 'source' => $filteredSource->getType()], - ]); - - $result = $collection->filterExcludeHidden(); - - self::assertInstanceOf(PaymentMeanCollection::class, $result); - self::assertCount(1, $result); - self::assertEquals($expected, iterator_to_array($result)[0]->getId()); - } - - /** @test */ - public function it_can_fetch_umbrella_payment_if_available(): void - { - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['source' => SourceType::adyen()->getType(), 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE], - ['source' => '1'], - ]); - - $result = $collection->fetchStoredMethodUmbrellaPaymentMean(); - - self::assertInstanceOf(PaymentMean::class, $result); - self::assertEquals(AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, $result->getValue('name')); - } - - /** @test */ - public function it_will_return_null_on_fetch_umbrella_if_payment_not_available(): void - { - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['source' => SourceType::adyen()->getType()], - ['source' => '1'], - ]); - - $result = $collection->fetchStoredMethodUmbrellaPaymentMean(); - - self::assertNull($result); - } - - /** @test */ - public function it_can_fetch_a_payment_by_stored_method_id(): void - { - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['id' => 123, 'source' => SourceType::adyen()->getType()], - ['id' => $expected = 456, 'source' => '1', 'stored_method_id' => $paymentMeanId = 'test123'], - ]); - - $result = $collection->fetchByStoredMethodId($paymentMeanId); - - self::assertInstanceOf(PaymentMean::class, $result); - self::assertEquals(1, $result->getSource()->getType()); - self::assertEquals($expected, $result->getId()); - } - - /** @test */ - public function it_can_fetch_a_payment_by_stored_method_umbrella_id(): void - { - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['id' => 123, 'source' => SourceType::adyen()->getType()], - ['id' => $expected = 456, 'source' => '1', 'stored_method_umbrella_id' => $paymentMeanId = 'test123'], - ]); - - $result = $collection->fetchByUmbrellaStoredMethodId($paymentMeanId); - - self::assertInstanceOf(PaymentMean::class, $result); - self::assertEquals(1, $result->getSource()->getType()); - self::assertEquals($expected, $result->getId()); - } - - /** @test */ - public function it_can_fetch_a_payment_by_payment_id(): void - { - $filteredSource = SourceType::adyen(); - $collection = PaymentMeanCollection::createFromShopwareArray([ - ['id' => $paymentMeanId = 123, 'source' => $filteredSource->getType()], - ['id' => '456', 'source' => '1'], - ]); - - $result = $collection->fetchById($paymentMeanId); - - self::assertInstanceOf(PaymentMean::class, $result); - self::assertEquals($paymentMeanId, $result->getId()); - } - - /** @test */ - public function it_returns_collection_in_shopware_array_format(): void - { - $filteredSource = SourceType::adyen(); - $paymentData = ['id' => '123', 'source' => $filteredSource->getType()]; - $expected = [$paymentData['id'] => $paymentData]; - $collection = PaymentMeanCollection::createFromShopwareArray([$paymentData]); - - $result = $collection->toShopwareArray(); - - self::assertEquals($expected, $result); - } -} diff --git a/tests/Unit/Collection/Payment/PaymentMethodCollectionTest.php b/tests/Unit/Collection/Payment/PaymentMethodCollectionTest.php deleted file mode 100644 index eaac913a..00000000 --- a/tests/Unit/Collection/Payment/PaymentMethodCollectionTest.php +++ /dev/null @@ -1,150 +0,0 @@ - [['type' => 'someType']]]); - - self::assertInstanceOf(\Countable::class, $result); - self::assertCount(1, $result); - } - - /** @test */ - public function it_can_map_with_a_callback(): void - { - $expectedMethod = [true, false]; - $collection = PaymentMethodCollection::fromAdyenMethods(['paymentMethods' => [ - ['type' => $filteredType = 'someType'], - ['type' => 'otherType'], - ]]); - - $result = $collection->map(static function(PaymentMethod $payment) use ($filteredType) { - return $filteredType === $payment->adyenType()->type(); - }); - - self::assertEquals($expectedMethod, $result); - } - - /** @test */ - public function it_can_map_to_raw(): void - { - $expected = [ - ['type' => 'someType'], - ['type' => 'otherType'], - ]; - $collection = PaymentMethodCollection::fromAdyenMethods(['paymentMethods' => $expected]); - - $result = $collection->mapToRaw(); - - self::assertEquals($expected, $result); - } - - /** @test */ - public function it_can_map_adyen_payment_methods(): void - { - $expectedMethod = PaymentMethod::fromRaw($paymentMethodData = ['type' => 'someType']); - $result = PaymentMethodCollection::fromAdyenMethods(['paymentMethods' => [$paymentMethodData]]); - - self::assertInstanceOf(PaymentMethodCollection::class, $result); - self::assertEquals($expectedMethod, $result->getIterator()->current()); - } - - /** @test */ - public function it_can_map_adyen_stored_payment_methods(): void - { - $expectedMethod = PaymentMethod::fromRaw($storedPaymentMethodData = ['type' => 'someType', 'id' => '1234']); - $result = PaymentMethodCollection::fromAdyenMethods(['storedPaymentMethods' => [$storedPaymentMethodData]]); - - self::assertInstanceOf(PaymentMethodCollection::class, $result); - self::assertEquals($expectedMethod, $result->getIterator()->current()); - } - - /** @test */ - public function it_can_enrich_with_import_locale(): void - { - $paymentMethod = PaymentMethod::fromRaw($paymentMethodData = [ - 'type' => 'someType', - 'name' => $name = 'someName', - ]); - $expectedMethod = $paymentMethod->withCode($name); - $paymentMethodsCollection = PaymentMethodCollection::fromAdyenMethods([ - 'paymentMethods' => [$paymentMethodData], - ]); - - $result = $paymentMethodsCollection->withImportLocale($paymentMethodsCollection); - - self::assertInstanceOf(PaymentMethodCollection::class, $result); - self::assertEquals($expectedMethod, $result->getIterator()->current()); - } - - /** @test */ - public function it_will_return_null_on_missing_methods_for_fetch_by_payment_mean(): void - { - $paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => 1, - 'source' => 1425514, - ]); - $collection = new PaymentMethodCollection(); - - $result = $collection->fetchByPaymentMean($paymentMean); - - self::assertNull($result); - } - - /** @test */ - public function it_can_fetch_a_method_by_payment_mean(): void - { - $attribute = new Attribute(); - $attribute->set(AdyenPayment::ADYEN_CODE, 'my_adyen_code'); - $paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => $methodStoredId = 'test_stored_method_id', - 'source' => 1425514, - 'attribute' => $attribute, - 'stored_method_id' => $methodStoredId, - ]); - $testPayment = PaymentMethod::fromRaw(['type' => 'someType']); - $expectedPayment = PaymentMethod::fromRaw(['type' => 'someType2', 'id' => $methodStoredId]); - $collection = new PaymentMethodCollection($testPayment, $expectedPayment); - - $result = $collection->fetchByPaymentMean($paymentMean); - - self::assertSame($expectedPayment, $result); - } - - /** @test */ - public function it_can_filter_with_a_callback(): void - { - $collection = PaymentMethodCollection::fromAdyenMethods(['paymentMethods' => [ - ['type' => $filteredType = 'someType'], - ['type' => 'otherType'], - ]]); - $expected = PaymentMethodCollection::fromAdyenMethods(['paymentMethods' => [ - ['type' => $filteredType], - ]]); - - $result = $collection->filter(static function(PaymentMethod $payment) use ($filteredType) { - return $filteredType === $payment->adyenType()->type(); - }); - - self::assertEquals($expected, $result); - } -} diff --git a/tests/Unit/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderTest.php b/tests/Unit/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderTest.php deleted file mode 100755 index 8038514f..00000000 --- a/tests/Unit/Components/Adyen/PaymentMethod/EnrichedPaymentMeanProviderTest.php +++ /dev/null @@ -1,292 +0,0 @@ -paymentMethodService = $this->prophesize(PaymentMethodServiceInterface::class); - $this->paymentMethodOptionsBuilder = $this->prophesize(PaymentMethodOptionsBuilderInterface::class); - $this->paymentMethodEnricher = $this->prophesize(PaymentMethodEnricherInterface::class); - - $this->provider = new EnrichedPaymentMeanProvider( - $this->paymentMethodService->reveal(), - $this->paymentMethodOptionsBuilder->reveal(), - $this->paymentMethodEnricher->reveal() - ); - } - - /** @test */ - public function it_is_an_enriched_payment_mean_provider(): void - { - $this->assertInstanceOf(EnrichedPaymentMeanProviderInterface::class, $this->provider); - } - - /** @test */ - public function it_does_not_enrich_on_empty_cart_value_and_excludes_adyen_methods(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMeanOne = PaymentMean::createFromShopwareArray([ - 'id' => 1, - 'source' => SourceType::shopwareDefault()->getType(), - ]), - PaymentMean::createFromShopwareArray([ - 'id' => 2, - 'source' => SourceType::adyen()->getType(), - ]) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn(['value' => 0.0]); - $this->paymentMethodService->getPaymentMethods((string)Argument::cetera())->shouldNotBeCalled(); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(1, $result); - $this->assertSame($paymentMeanOne, iterator_to_array($result)[0]); - } - - /** @test */ - public function it_throws_an_exception_on_missing_umbrella_payment(): void - { - $adyenIdentifier = sprintf('%s_%s', $adyenType = 'non', $adyenName = 'adyen'); - $paymentMeans = new PaymentMeanCollection( - $paymentMeanOne = PaymentMean::createFromShopwareArray([ - 'id' => 1, - 'source' => SourceType::shopwareDefault()->getType(), - ]) - ); - - // filled with a matching identifier, to catch if the early returns fails - $adyenPaymentMethods = new PaymentMethodCollection( - PaymentMethod::fromRaw(['type' => $adyenType])->withCode($adyenName) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'BE', - 'currency' => $currency = 'EUR', - 'value' => $value = 17.7, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn($adyenPaymentMethods); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $this->expectException(UmbrellaPaymentMeanNotFoundException::class); - - $result = $this->provider->__invoke($paymentMeans); - } - - /** @test */ - public function it_does_not_enrich_non_adyen_methods(): void - { - $adyenIdentifier = sprintf('%s_%s', $adyenType = 'non', $adyenName = 'adyen'); - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => 17, - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - 'source' => SourceType::shopwareDefault()->getType(), - 'attribute' => new Attribute([ - 'adyen_type' => $adyenIdentifier, - ]), - ]) - ); - - // filled with a matching identifier, to catch if the early returns fails - $adyenPaymentMethods = new PaymentMethodCollection( - PaymentMethod::fromRaw(['type' => $adyenType])->withCode($adyenName) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'BE', - 'currency' => $currency = 'EUR', - 'value' => $value = 17.7, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn($adyenPaymentMethods); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(1, $result); - $this->assertSame($paymentMean, iterator_to_array($result)[0]); - } - - /** @test */ - public function it_does_not_enrich_and_removes_payment_means_without_attribute(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMeanOne = PaymentMean::createFromShopwareArray([ - 'id' => 19, - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - 'source' => SourceType::shopwareDefault()->getType(), - ]), - $paymentMeanTwo = PaymentMean::createFromShopwareArray([ - 'id' => 21, - 'source' => SourceType::adyen()->getType(), - ]) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'BE', - 'currency' => $currency = 'EUR', - 'value' => $value = 17.7, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn(new PaymentMethodCollection()); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(1, $result); - $this->assertEquals($paymentMeanOne, iterator_to_array($result)[0]); - } - - /** @test */ - public function it_does_not_enrich_payment_means_with_attribute_null_values(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => 9, - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - 'source' => SourceType::adyen()->getType(), - 'attribute' => new Attribute([ - 'adyen_type' => null, - ]), - ]) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'GB', - 'currency' => $currency = 'GBP', - 'value' => $value = 9.39, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn(new PaymentMethodCollection()); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(0, $result); - } - - /** @test */ - public function it_removes_adyen_payment_means_without_matching_adyen_payment_method(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => 25, - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - 'source' => SourceType::adyen()->getType(), - 'attribute' => new Attribute([ - 'adyen_type' => 'non_matching_adyen_identifier', - ]), - ]) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'BE', - 'currency' => $currency = 'EUR', - 'value' => $value = 17.7, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn(new PaymentMethodCollection()); - $this->paymentMethodEnricher->__invoke(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(0, $result); - } - - /** @test */ - public function it_enriches_adyen_payment_methods(): void - { - $adyenIdentifier = sprintf('%s_%s', $adyenType = 'bcmc', $adyenName = 'adyen_name'); - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray($raw = [ - 'id' => $id = 15, - 'source' => $source = SourceType::adyen()->getType(), - 'attribute' => new Attribute([ - 'adyen_type' => $adyenIdentifier, - ]), - ]), - $umbrellaMean = PaymentMean::createFromShopwareArray([ - 'id' => 25, - 'source' => SourceType::adyen()->getType(), - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - ]) - ); - - $adyenPaymentMethods = new PaymentMethodCollection( - $paymentMethod = PaymentMethod::fromRaw([ - 'type' => $adyenType, - ])->withCode($adyenName), - $storedPaymentMethod = PaymentMethod::fromRaw($storedRaw = [ - 'id' => $storedMethodId = 'adyen-stored-payment-method-id', - 'type' => $schemaType = 'scheme', - ]) - ); - - $this->paymentMethodOptionsBuilder->__invoke()->willReturn([ - 'countryCode' => $countryCode = 'DE', - 'currency' => $currency = 'EUR', - 'value' => $value = 15.0, - ]); - $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value) - ->willReturn($adyenPaymentMethods); - - $this->paymentMethodEnricher->__invoke($raw, $paymentMethod)->willReturn($rawEnriched = [ - 'id' => $id, - 'source' => $source, - 'enriched' => true, - 'adyenType' => $adyenType, - ]); - - $this->paymentMethodEnricher->__invoke($umbrellaMean->getRaw(), $storedPaymentMethod)->willReturn($storedRawEnriched = [ - 'id' => $storedMethodId, - 'adyenType' => $schemaType, - 'source' => $source, - ]); - - $result = $this->provider->__invoke($paymentMeans); - - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(2, $result); - $this->assertEquals($rawEnriched, iterator_to_array($result)[0]->getRaw()); - $this->assertEquals($storedRawEnriched, iterator_to_array($result)[1]->getRaw()); - } -} diff --git a/tests/Unit/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderTest.php b/tests/Unit/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderTest.php deleted file mode 100755 index 47ee2916..00000000 --- a/tests/Unit/Components/Adyen/PaymentMethod/StoredPaymentMeanProviderTest.php +++ /dev/null @@ -1,84 +0,0 @@ -enrichedPaymentMeanProvider = $this->prophesize(EnrichedPaymentMeanProviderInterface::class); - $this->connection = $this->prophesize(Connection::class); - - $this->storedPaymentMeanProvider = new StoredPaymentMeanProvider( - $this->enrichedPaymentMeanProvider->reveal(), - $this->connection->reveal() - ); - } - - /** @test */ - public function it_is_an_stored_payment_mean_provider(): void - { - $this->assertInstanceOf(StoredPaymentMeanProviderInterface::class, $this->storedPaymentMeanProvider); - } - - /** @test */ - public function it_will_return_null_on_missing_params(): void - { - $request = $this->prophesize(Enlight_Controller_Request_Request::class); - $request->getParam('register', [])->willReturn([]); - - $result = $this->storedPaymentMeanProvider->fromRequest($request->reveal()); - - self::assertNull($result); - } - - /** @test */ - public function it_will_try_to_provide_a_payment_by_umbrella_stored_method_id(): void - { - $request = $this->prophesize(Enlight_Controller_Request_Request::class); - $request->getParam('register', [])->willReturn(['payment' => $id = 'stored_method_umbrella_id']); - - $emptyCollection = PaymentMeanCollection::createFromShopwareArray([]); - $this->enrichedPaymentMeanProvider->__invoke($emptyCollection)->willReturn($emptyCollection); - $queryBuilder = $this->prophesize(QueryBuilder::class); - $queryBuilder->select('*')->willReturn($queryBuilder); - $queryBuilder->from('s_core_paymentmeans')->willReturn($queryBuilder); - $queryBuilder->where('name = :umbrellaMethodName')->willReturn($queryBuilder); - $queryBuilder->setParameter(':umbrellaMethodName', AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE)->willReturn($queryBuilder); - $driverResultStatement = $this->prophesize(DriverResultStatement::class); - $driverResultStatement->fetchAll()->willReturn([]); - - $queryBuilder->execute()->willReturn($driverResultStatement->reveal()); - $this->connection->createQueryBuilder()->willReturn($queryBuilder->reveal()); - - $result = $this->storedPaymentMeanProvider->fromRequest($request->reveal()); - - self::assertNull($result); - } -} diff --git a/tests/Unit/Components/Adyen/PaymentMethod/TraceableEnrichedPaymentMeanProviderTest.php b/tests/Unit/Components/Adyen/PaymentMethod/TraceableEnrichedPaymentMeanProviderTest.php deleted file mode 100755 index f4f7250b..00000000 --- a/tests/Unit/Components/Adyen/PaymentMethod/TraceableEnrichedPaymentMeanProviderTest.php +++ /dev/null @@ -1,78 +0,0 @@ -enrichedPaymentMeanProvider = $this->prophesize(EnrichedPaymentMeanProviderInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - - $this->provider = new TraceableEnrichedPaymentMeanProvider( - $this->enrichedPaymentMeanProvider->reveal(), - $this->logger->reveal() - ); - } - - /** @test */ - public function it_provides_enriched_payment_means(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray([ - 'source' => SourceType::adyen()->getType(), - ]) - ); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willReturn( - $enriched = new PaymentMeanCollection($paymentMean) - ); - $this->logger->critical(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertSame($enriched, $result); - } - - /** @test */ - public function it_logs_silently_exceptions(): void - { - $paymentMeans = new PaymentMeanCollection( - $paymentMean = PaymentMean::createFromShopwareArray([ - 'source' => SourceType::adyen()->getType(), - ]) - ); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willThrow( - $exception = new \Exception($message = 'invalid type') - ); - - $this->logger->critical($message, ['exception' => $exception])->shouldBeCalled(); - - $result = $this->provider->__invoke($paymentMeans); - $this->assertNotSame($paymentMeans, $result); - $this->assertInstanceOf(PaymentMeanCollection::class, $result); - $this->assertCount(0, $result); - } -} diff --git a/tests/Unit/Components/Manager/UserPreferenceManagerTest.php b/tests/Unit/Components/Manager/UserPreferenceManagerTest.php deleted file mode 100755 index 37f2dd59..00000000 --- a/tests/Unit/Components/Manager/UserPreferenceManagerTest.php +++ /dev/null @@ -1,50 +0,0 @@ -modelManager = $this->prophesize(EntityManager::class); - - $this->userPreferenceManager = new UserPreferenceManager($this->modelManager->reveal()); - } - - /** @test */ - public function it_is_an_user_preference_manager(): void - { - $this->assertInstanceOf(UserPreferenceManagerInterface::class, $this->userPreferenceManager); - } - - /** @test */ - public function it_can_save_a_record(): void - { - $userPreference = new UserPreference(); - $userPreference->setUserId(1234); - $userPreference->setStoredMethodId('expected-method-id'); - - $this->modelManager->persist($userPreference)->shouldBeCalled(); - $this->modelManager->flush($userPreference)->shouldBeCalled(); - - $this->userPreferenceManager->save($userPreference); - } -} diff --git a/tests/Unit/Components/Payload/Providers/RecurringOneOffPaymentTokenProviderTest.php b/tests/Unit/Components/Payload/Providers/RecurringOneOffPaymentTokenProviderTest.php deleted file mode 100755 index 56ffa274..00000000 --- a/tests/Unit/Components/Payload/Providers/RecurringOneOffPaymentTokenProviderTest.php +++ /dev/null @@ -1,60 +0,0 @@ -recurringOneOffPaymentTokenProvider = new RecurringOneOffPaymentTokenProvider(); - $this->paymentContext = $this->prophesize(PaymentContext::class); - } - - /** @test */ - public function it_is_a_recurring_payment_payload_provider(): void - { - self::assertInstanceOf(PaymentPayloadProvider::class, $this->recurringOneOffPaymentTokenProvider); - } - - /** @test */ - public function it_will_return_empty_for_none_stored_payment_method(): void - { - $this->paymentContext->getPaymentInfo()->willReturn([]); - - $result = $this->recurringOneOffPaymentTokenProvider->provide($this->paymentContext->reveal()); - - self::assertEquals([], $result); - } - - /** @test */ - public function it_can_return_the_recurring_one_off_payment_token_data(): void - { - $this->paymentContext->getPaymentInfo()->willReturn(['storedPaymentMethodId' => 'stored-method-id']); - - $result = $this->recurringOneOffPaymentTokenProvider->provide($this->paymentContext->reveal()); - - self::assertEquals([ - 'shopperInteraction' => ShopperInteraction::ecommerce()->shopperInteraction(), - 'recurringProcessingModel' => RecurringProcessingModel::cardOnFile()->recurringProcessingModel(), - ], $result); - } -} diff --git a/tests/Unit/Components/Payload/Providers/RecurringPaymentProviderTest.php b/tests/Unit/Components/Payload/Providers/RecurringPaymentProviderTest.php deleted file mode 100755 index 6376eb09..00000000 --- a/tests/Unit/Components/Payload/Providers/RecurringPaymentProviderTest.php +++ /dev/null @@ -1,60 +0,0 @@ -recurringPaymentProvider = new RecurringPaymentProvider(); - $this->paymentContext = $this->prophesize(PaymentContext::class); - } - - /** @test */ - public function it_is_a_recurring_payment_payload_provider(): void - { - self::assertInstanceOf(PaymentPayloadProvider::class, $this->recurringPaymentProvider); - } - - /** @test */ - public function it_will_return_empty_for_none_stored_payment_method(): void - { - $this->paymentContext->getPaymentInfo()->willReturn([]); - - $result = $this->recurringPaymentProvider->provide($this->paymentContext->reveal()); - - self::assertEquals([], $result); - } - - /** @test */ - public function it_can_return_the_recurring_one_off_payment_token_data(): void - { - $this->paymentContext->getPaymentInfo()->willReturn(['storedPaymentMethodId' => 'stored-method-id']); - - $result = $this->recurringPaymentProvider->provide($this->paymentContext->reveal()); - - self::assertEquals([ - 'shopperInteraction' => ShopperInteraction::contAuth()->shopperInteraction(), - 'recurringProcessingModel' => RecurringProcessingModel::cardOnFile()->recurringProcessingModel(), - ], $result); - } -} diff --git a/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php b/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php deleted file mode 100755 index f240077c..00000000 --- a/tests/Unit/Enricher/Payment/PaymentMethodEnricherTest.php +++ /dev/null @@ -1,120 +0,0 @@ -snippets = $this->prophesize(Shopware_Components_Snippet_Manager::class); - $this->imageLogoProvider = $this->prophesize(ImageLogoProviderInterface::class); - - $this->paymentMethodEnricher = new PaymentMethodEnricher( - $this->snippets->reveal(), - $this->imageLogoProvider->reveal() - ); - } - - /** @test */ - public function it_is_a_payment_method_enricher(): void - { - $this->assertInstanceOf(PaymentMethodEnricherInterface::class, $this->paymentMethodEnricher); - } - - /** @test */ - public function it_will_enrich_a_payment_method_without_stored_method_data(): void - { - $shopwareMethod = [ - 'id' => 'shopware-method-id', - 'additionaldescription' => $description = 'Adyen Method', - 'image' => '', - ]; - $paymentMethod = PaymentMethod::fromRaw($rawData = [ - 'code' => 'test_method', - 'type' => 'test_type', - ]); - $snippetsNamespace = $this->prophesize(\Enlight_Components_Snippet_Namespace::class); - $snippetsNamespace->get($paymentMethod->adyenType()->type())->willReturn($description = 'Adyen Method'); - $this->snippets->getNamespace('adyen/method/description')->willReturn($snippetsNamespace); - $this->imageLogoProvider->provideByType($paymentMethod->adyenType()->type())->willReturn($image = 'image'); - - $result = ($this->paymentMethodEnricher)($shopwareMethod, $paymentMethod); - - $expected = [ - 'id' => 'shopware-method-id', - 'additionaldescription' => $description, - 'image' => $image, - 'enriched' => true, - 'isStoredPayment' => false, - 'isAdyenPaymentMethod' => true, - 'adyenType' => $paymentMethod->adyenType()->type(), - 'metadata' => $rawData, - ]; - - self::assertEquals($expected, $result); - } - - /** @test */ - public function it_will_enrich_a_payment_method_with_stored_method_data(): void - { - $shopwareMethod = [ - 'id' => $shopwareMethodId = 'shopware-method-id', - 'additionaldescription' => $description = 'Stored Method', - ]; - $paymentMethod = PaymentMethod::fromRaw($rawData = [ - 'id' => $storedMethodId = 'stored_method_id', - 'name' => $storedMethodName = 'stored method name', - 'code' => 'test_method', - 'type' => 'test_type', - 'lastFour' => $lastFour = '1234', - ]); - $snippetsNamespace = $this->prophesize(\Enlight_Components_Snippet_Namespace::class); - $snippetsNamespace->get($paymentMethod->adyenType()->type())->willReturn($description); - $snippetsNamespace->get('CardNumberEndingOn', $text = 'Card number ending on', true)->willReturn($text); - $this->snippets->getNamespace('adyen/method/description')->willReturn($snippetsNamespace); - $this->snippets->getNamespace('adyen/checkout/payment')->willReturn($snippetsNamespace); - $this->imageLogoProvider->provideByType($paymentMethod->adyenType()->type())->willReturn($image = 'image'); - - $result = ($this->paymentMethodEnricher)($shopwareMethod, $paymentMethod); - - $expected = [ - 'id' => 'shopware-method-id', - 'additionaldescription' => sprintf('%s%s: %s', $description.' ', $text, $lastFour), - 'image' => $image, - 'enriched' => true, - 'isStoredPayment' => true, - 'isAdyenPaymentMethod' => true, - 'adyenType' => $paymentMethod->adyenType()->type(), - 'metadata' => $rawData, - 'stored_method_umbrella_id' => sprintf('%s_%s', $shopwareMethodId, $storedMethodId), - 'stored_method_id' => $storedMethodId, - 'description' => $storedMethodName, - 'source' => SourceType::adyen()->getType(), - ]; - - self::assertEquals($expected, $result); - } -} diff --git a/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php b/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php deleted file mode 100755 index 261a1dfe..00000000 --- a/tests/Unit/Exceptions/DuplicateNotificationExceptionTest.php +++ /dev/null @@ -1,50 +0,0 @@ -exception = new DuplicateNotificationException(); - } - - /** @test */ - public function is_a_runtime_exception(): void - { - self::assertInstanceOf(\RuntimeException::class, $this->exception); - } - - /** @test */ - public function it_can_be_constructed_with_a_notification(): void - { - $notification = new Notification(); - $notification - ->setId($id = 1) - ->setOrderId($orderId = 2) - ->setPspReference($pspReference = 'PSP_REF_1') - ->setStatus('received') - ->setPaymentMethod('mc') - ->setEventCode('AUTHORISATION') - ->setSuccess(true) - ->setMerchantAccountCode('Adyen-test') - ->setAmountValue(4598.0000) - ->setAmountCurrency('EUR'); - - $exception = DuplicateNotificationException::withNotification($notification); - - self::assertInstanceOf(DuplicateNotificationException::class, $exception); - self::assertEquals('Duplicate notification is not handled. Notification with id: "1", orderId: "2", pspReference: "PSP_REF_1", status: "received", paymentMethod: "mc", eventCode: "AUTHORISATION", success: "1", merchantAccountCode: "Adyen-test", amountValue: "4598", amountCurrency: "EUR"', - $exception->getMessage() - ); - } -} diff --git a/tests/Unit/Exceptions/RecurringPaymentTokenNotFoundExceptionTest.php b/tests/Unit/Exceptions/RecurringPaymentTokenNotFoundExceptionTest.php deleted file mode 100755 index 562bb42b..00000000 --- a/tests/Unit/Exceptions/RecurringPaymentTokenNotFoundExceptionTest.php +++ /dev/null @@ -1,56 +0,0 @@ -exception = new RecurringPaymentTokenNotFoundException(); - } - - /** @test */ - public function is_a_runtime_exception(): void - { - self::assertInstanceOf(\RuntimeException::class, $this->exception); - } - - /** @test */ - public function it_can_be_constructed_with_customer_id_and_order_number(): void - { - $exception = RecurringPaymentTokenNotFoundException::withCustomerIdAndOrderNumber( - $customerId = 'customer-id', - $orderNumber = 'order-number' - ); - - self::assertInstanceOf(RecurringPaymentTokenNotFoundException::class, $exception); - self::assertEquals( - 'Recurring payment token not found with customer id: "'.$customerId.'", order number: "'.$orderNumber.'"', - $exception->getMessage() - ); - } - - /** @test */ - public function it_can_be_constructed_with_psp_reference(): void - { - $exception = RecurringPaymentTokenNotFoundException::withPendingResultCodeAndPspReference( - $pspReference = 'psp-reference' - ); - - self::assertInstanceOf(RecurringPaymentTokenNotFoundException::class, $exception); - self::assertEquals( - 'Recurring payment token not found with result code: "'.PaymentResultCode::pending()->resultCode() - .'", psp reference: "'.$pspReference.'"', - $exception->getMessage() - ); - } -} diff --git a/tests/Unit/Exceptions/RecurringPaymentTokenNotSavedExceptionTest.php b/tests/Unit/Exceptions/RecurringPaymentTokenNotSavedExceptionTest.php deleted file mode 100755 index dd951165..00000000 --- a/tests/Unit/Exceptions/RecurringPaymentTokenNotSavedExceptionTest.php +++ /dev/null @@ -1,40 +0,0 @@ -exception = new RecurringPaymentTokenNotSavedException(); - } - - /** @test */ - public function is_a_runtime_exception(): void - { - self::assertInstanceOf(\RuntimeException::class, $this->exception); - } - - /** @test */ - public function it_can_be_constructed_with_token_identifier(): void - { - $tokenIdentifier = TokenIdentifier::generate(); - - $exception = RecurringPaymentTokenNotSavedException::withId($tokenIdentifier); - - self::assertInstanceOf(RecurringPaymentTokenNotSavedException::class, $exception); - self::assertEquals( - 'Recurring payment token not saved with id: "'.$tokenIdentifier->identifier().'"', - $exception->getMessage() - ); - } -} diff --git a/tests/Unit/Http/Response/FrontendJsonResponseTest.php b/tests/Unit/Http/Response/FrontendJsonResponseTest.php deleted file mode 100755 index c7892282..00000000 --- a/tests/Unit/Http/Response/FrontendJsonResponseTest.php +++ /dev/null @@ -1,57 +0,0 @@ -apiJsonResponse = new FrontendJsonResponse(); - } - - /** @test */ - public function it_is_an_api_json_response(): void - { - self::assertInstanceOf(ApiJsonResponse::class, $this->apiJsonResponse); - } - - /** @test */ - public function it_can_send_a_json_response(): void - { - $frontController = $this->prophesize(\Enlight_Controller_Front::class); - $httpResponse = $this->prophesize(\Enlight_Controller_Response_ResponseHttp::class); - $response = new JsonResponse([], Response::HTTP_OK); - $plugins = $this->prophesize(\Enlight_Plugin_Namespace_Loader::class); - $viewRenderer = $this->prophesize(\Enlight_Controller_Plugins_ViewRenderer_Bootstrap::class); - - $viewRenderer->setNoRender()->shouldBeCalled(); - $plugins->ViewRenderer()->willReturn($viewRenderer); - $frontController->Plugins()->willReturn($plugins); - - $httpResponse->setHeader('Content-type', $response->headers->get('Content-Type'), true)->shouldBeCalled(); - $httpResponse->setHttpResponseCode(Response::HTTP_OK)->shouldBeCalled(); - $httpResponse->setBody($response->getContent())->shouldBeCalled(); - - $result = $this->apiJsonResponse->sendJsonResponse( - $frontController->reveal(), - $httpResponse->reveal(), - $response - ); - - self::assertSame($httpResponse->reveal(), $result); - } -} diff --git a/tests/Unit/Mock/ControllerActionMock.php b/tests/Unit/Mock/ControllerActionMock.php deleted file mode 100644 index d32ff66f..00000000 --- a/tests/Unit/Mock/ControllerActionMock.php +++ /dev/null @@ -1,9 +0,0 @@ -group = PaymentGroup::default(); - } - - /** @test */ - public function it_contains_a_group(): void - { - $this->assertEquals('payment', $this->group->group()); - } - - /** @test */ - public function it_knows_it_equals_default_group(): void - { - $this->assertTrue($this->group->equals(PaymentGroup::default())); - } - - /** @test */ - public function it_can_be_constructed_by_stored(): void - { - $group = PaymentGroup::stored(); - $this->assertEquals('stored', $group->group()); - $this->assertTrue($group->equals(PaymentGroup::stored())); - } -} diff --git a/tests/Unit/Models/Payment/PaymentMeanTest.php b/tests/Unit/Models/Payment/PaymentMeanTest.php deleted file mode 100755 index bb3080b3..00000000 --- a/tests/Unit/Models/Payment/PaymentMeanTest.php +++ /dev/null @@ -1,126 +0,0 @@ -paymentMean = PaymentMean::createFromShopwareArray([ - 'id' => '15', - 'source' => '1425514', - 'attribute' => new Attribute([ - 'adyen_type' => 'adyen-type', - ]), - 'enriched' => true, - 'adyenType' => 'adyen-type', - 'hide' => true, - ]); - } - - /** @test */ - public function it_contains_an_id(): void - { - $this->assertIsInt($this->paymentMean->getId()); - $this->assertEquals(15, $this->paymentMean->getId()); - } - - /** @test */ - public function it_contains_a_source(): void - { - $this->assertInstanceOf(SourceType::class, $this->paymentMean->getSource()); - $this->assertEquals(SourceType::adyen(), $this->paymentMean->getSource()); - } - - /** @test */ - public function it_knows_it_is_hidden(): void - { - $this->assertTrue($this->paymentMean->isHidden()); - } - - /** @test */ - public function it_contains_raw_data(): void - { - $this->assertIsArray($this->paymentMean->getRaw()); - $this->assertEquals([ - 'id' => '15', - 'source' => '1425514', - 'attribute' => new Attribute([ - 'adyen_type' => 'adyen-type', - ]), - 'enriched' => true, - 'adyenType' => 'adyen-type', - 'hide' => true, - ], $this->paymentMean->getRaw()); - } - - /** @test */ - public function it_knows_when_enriched(): void - { - $this->assertTrue($this->paymentMean->isEnriched()); - } - - /** @test */ - public function it_contains_adyen_type(): void - { - $this->assertTrue($this->paymentMean->adyenType()->equals(PaymentType::load('adyen-type'))); - } - - /** @test */ - public function it_can_retrieve_a_value(): void - { - $this->assertIsString($this->paymentMean->getValue('id')); - $this->assertEquals('15', $this->paymentMean->getValue('id')); - } - - /** @test */ - public function it_can_retrieve_a_value_with_default_fallback(): void - { - $this->assertNull($this->paymentMean->getValue('non-existent')); - } - - /** @test */ - public function it_can_retrieve_a_value_with_fallback(): void - { - $this->assertEquals('fallback', $this->paymentMean->getValue('non-existent', 'fallback')); - } - - /** @test */ - public function it_can_retrieve_an_attribute(): void - { - $this->assertEquals(new Attribute([ - 'adyen_type' => 'adyen-type', - ]), $this->paymentMean->getAttribute()); - } - - /** @test */ - public function it_can_retrieve_attribute_adyen_type(): void - { - $this->assertEquals('adyen-type', $this->paymentMean->getAdyenCode()); - } - - /** @test */ - public function it_can_retrieve_default_attribute_adyen_type(): void - { - $paymentMean = PaymentMean::createFromShopwareArray(['source' => null]); - $this->assertEquals('', $paymentMean->getAdyenCode()); - } - - /** @test */ - public function it_can_retrieve_default_attribute_adyen_stored_method_id(): void - { - $paymentMean = PaymentMean::createFromShopwareArray(['source' => null]); - $this->assertEquals('', $paymentMean->getAdyenStoredMethodId()); - } -} diff --git a/tests/Unit/Models/Payment/PaymentMethodTest.php b/tests/Unit/Models/Payment/PaymentMethodTest.php deleted file mode 100755 index 4b57c6d7..00000000 --- a/tests/Unit/Models/Payment/PaymentMethodTest.php +++ /dev/null @@ -1,118 +0,0 @@ -paymentMethod = PaymentMethod::fromRaw([ - 'type' => 'bcmc', - 'name' => 'Bancontact', - 'details' => [ - 'key' => 'encryptedCardNumber', - ], - ]); - } - - /** @test */ - public function it_can_be_constructed_with_code(): void - { - $paymentMethod = $this->paymentMethod->withCode('adyen-code'); - - $this->assertNotSame($this->paymentMethod, $paymentMethod); - $this->assertEquals('bcmc_adyen_code', $paymentMethod->code()); - } - - /** @test */ - public function it_contains_a_type(): void - { - $this->assertEquals('bcmc', $this->paymentMethod->adyenType()->type()); - } - - /** @test */ - public function it_contains_a_group(): void - { - $this->assertEquals('payment', $this->paymentMethod->group()->group()); - } - - /** @test */ - public function it_contains_raw_data(): void - { - $this->assertEquals([ - 'type' => 'bcmc', - 'name' => 'Bancontact', - 'details' => [ - 'key' => 'encryptedCardNumber', - ], - ], $this->paymentMethod->rawData()); - } - - /** @test */ - public function it_contains_a_adyen_name(): void - { - $this->assertEquals('Bancontact', $this->paymentMethod->name()); - } - - /** @test */ - public function it_contains_a_stored_payment_method_id(): void - { - $this->assertEquals('', $this->paymentMethod->getStoredPaymentMethodId()); - } - - /** @test */ - public function it_know_it_is_a_stored_payment(): void - { - $this->assertFalse($this->paymentMethod->isStoredPayment()); - } - - /** @test */ - public function it_contains_details(): void - { - $this->assertTrue($this->paymentMethod->hasDetails()); - } - - /** @test */ - public function it_can_serialize_minimal_state(): void - { - $this->assertTrue($this->paymentMethod->hasDetails()); - } - - /** @test */ - public function it_can_retrieve_values_with_default_fallback(): void - { - $this->assertNull($this->paymentMethod->getValue('non-exisiting-key')); - } - - /** @test */ - public function it_can_retrieve_values_with_fallback(): void - { - $this->assertEquals('fallback-value', $this->paymentMethod->getValue('non-exisiting-key', 'fallback-value')); - } - - /** - * @test - * @dataProvider valueDataProvider - * - * @param mixed $expected - */ - public function it_can_retrieve_values($expected, string $key): void - { - $this->assertEquals($expected, $this->paymentMethod->getValue($key)); - } - - public function valueDataProvider(): iterable - { - yield ['bcmc', 'type']; - yield ['Bancontact', 'name']; - yield [['key' => 'encryptedCardNumber'], 'details']; - } -} diff --git a/tests/Unit/Models/Payment/PaymentTypeTest.php b/tests/Unit/Models/Payment/PaymentTypeTest.php deleted file mode 100755 index 9dc659a5..00000000 --- a/tests/Unit/Models/Payment/PaymentTypeTest.php +++ /dev/null @@ -1,47 +0,0 @@ -type = PaymentType::googlePay(); - } - - /** @test */ - public function it_contains_a_type(): void - { - $this->assertEquals('paywithgoogle', $this->type->type()); - } - - /** @test */ - public function it_knows_it_equals_google_pay_type(): void - { - $this->assertTrue($this->type->equals(PaymentType::googlePay())); - } - - /** @test */ - public function it_can_construct_type_apple_pay(): void - { - $type = PaymentType::applePay(); - $this->assertEquals('applepay', $type->type()); - $this->assertTrue($type->equals(PaymentType::applePay())); - } - - /** @test */ - public function it_can_be_constructed_by_load(): void - { - $paymentType = PaymentType::load($type = 'any-type'); - $this->assertEquals($type, $paymentType->type()); - $this->assertTrue($paymentType->equals(PaymentType::load($type))); - } -} diff --git a/tests/Unit/Models/PaymentMethod/ImportResultTest.php b/tests/Unit/Models/PaymentMethod/ImportResultTest.php deleted file mode 100755 index d840e43f..00000000 --- a/tests/Unit/Models/PaymentMethod/ImportResultTest.php +++ /dev/null @@ -1,87 +0,0 @@ -importResult = ImportResult::success( - new Shop(), - PaymentMethod::fromRaw([]), - ImportStatus::created() - ); - } - - /** @test */ - public function it_contains_a_shop(): void - { - $this->assertEquals(new Shop(), $this->importResult->getShop()); - } - - /** @test */ - public function it_contains_success(): void - { - $this->assertTrue($this->importResult->isSuccess()); - } - - /** @test */ - public function it_contains_a_payment_method(): void - { - $this->assertEquals(PaymentMethod::fromRaw([]), $this->importResult->getPaymentMethod()); - } - - /** @test */ - public function it_contains_a_exception(): void - { - $this->assertNull($this->importResult->getException()); - } - - /** @test */ - public function it_contains_a_status(): void - { - $this->assertEquals(ImportStatus::created(), $this->importResult->getStatus()); - } - - /** @test */ - public function it_can_be_constructed_by_success_sub_shop_fallback(): void - { - $result = ImportResult::successSubShopFallback( - $shop = new Shop(), - $status = ImportStatus::updated() - ); - - $this->assertSame($shop, $result->getShop()); - $this->assertTrue($result->isSuccess()); - $this->assertNull($result->getPaymentMethod()); - $this->assertNull($result->getException()); - $this->assertSame($status, $result->getStatus()); - } - - /** @test */ - public function it_can_be_constructed_from_exception(): void - { - $result = ImportResult::fromException( - $shop = new Shop(), - $paymentMethod = PaymentMethod::fromRaw([]), - $exception = new \Exception('message') - ); - - $this->assertSame($shop, $result->getShop()); - $this->assertFalse($result->isSuccess()); - $this->assertEquals($paymentMethod, $result->getPaymentMethod()); - $this->assertEquals($exception, $result->getException()); - $this->assertEquals(ImportStatus::notHandledStatus(), $result->getStatus()); - } -} diff --git a/tests/Unit/Models/PaymentResultCodeTest.php b/tests/Unit/Models/PaymentResultCodeTest.php deleted file mode 100755 index ab092a37..00000000 --- a/tests/Unit/Models/PaymentResultCodeTest.php +++ /dev/null @@ -1,91 +0,0 @@ -paymentResultCode = PaymentResultCode::authorised(); - } - - /** @test */ - public function it_knows_when_it_equals_payment_result_codes_objects(): void - { - $this->assertTrue($this->paymentResultCode->equals(PaymentResultCode::authorised())); - $this->assertFalse($this->paymentResultCode->equals(PaymentResultCode::invalid())); - } - - /** @test */ - public function it_is_immutable_constructed(): void - { - $paymentResultCodeAuthorised = PaymentResultCode::authorised(); - $this->assertEquals($this->paymentResultCode, $paymentResultCodeAuthorised); - $this->assertNotSame($this->paymentResultCode, $paymentResultCodeAuthorised); - } - - /** @test */ - public function it_throws_an_invalid_argument_exception_when_result_code_is_unknown(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid result code: "INVALID_CODE"'); - - PaymentResultCode::load('INVALID_CODE'); - } - - /** @test */ - public function it_can_load_a_result_code(): void - { - $this->assertEquals( - PaymentResultCode::authorised(), - PaymentResultCode::load('Authorised') - ); - } - - /** @test */ - public function it_knows_when_a_result_code_exists(): void - { - $result = PaymentResultCode::exists('Authorised'); - - $this->assertTrue($result); - } - - /** @test */ - public function it_knows_when_a_result_code_doesnt_exists(): void - { - $result = PaymentResultCode::exists('invalid-code-test'); - - $this->assertFalse($result); - } - - /** - * @dataProvider resultCodeProvider - * @test - */ - public function it_can_be_constructed_with_named_constructors(PaymentResultCode $resultCode, string $code): void - { - $this->assertEquals($code, $resultCode->resultCode()); - } - - public function resultCodeProvider(): \Generator - { - yield [PaymentResultCode::authorised(), 'Authorised']; - yield [PaymentResultCode::cancelled(), 'Cancelled']; - yield [PaymentResultCode::challengeShopper(), 'ChallengeShopper']; - yield [PaymentResultCode::error(), 'Error']; - yield [PaymentResultCode::invalid(), 'Invalid']; - yield [PaymentResultCode::identifyShopper(), 'IdentifyShopper']; - yield [PaymentResultCode::pending(), 'Pending']; - yield [PaymentResultCode::received(), 'Received']; - yield [PaymentResultCode::redirectShopper(), 'RedirectShopper']; - yield [PaymentResultCode::refused(), 'Refused']; - } -} diff --git a/tests/Unit/Models/RecurringPayment/RecurringPaymentTokenTest.php b/tests/Unit/Models/RecurringPayment/RecurringPaymentTokenTest.php deleted file mode 100755 index b9c8261a..00000000 --- a/tests/Unit/Models/RecurringPayment/RecurringPaymentTokenTest.php +++ /dev/null @@ -1,145 +0,0 @@ -recurringPaymentToken = RecurringPaymentToken::create( - $tokenIdentifier = TokenIdentifier::generateFromString($knownUuid = '033a6dad-5a58-4b74-b420-6772bab3946e'), - $customerId = 'YOUR_UNIQUE_SHOPPER_ID_IOfW3k9G2PvXFu2j', - $recurringDetailReference = '8415698462516992', - $pspReference = '8515815919501547', - $orderNumber = 'YOUR_ORDER_NUMBER', - $resultCode = PaymentResultCode::authorised(), - $amountValue = 10500, - $amountCurrency = 'EUR' - ); - } - - /** @test */ - public function it_is_a_model_entity(): void - { - $this->assertInstanceOf(ModelEntity::class, $this->recurringPaymentToken); - } - - /** @test */ - public function it_contains_an_id(): void - { - $this->assertEquals('033a6dad-5a58-4b74-b420-6772bab3946e', $this->recurringPaymentToken->id()); - } - - /** @test */ - public function it_contains_a_token_identifier(): void - { - $this->assertEquals( - TokenIdentifier::generateFromString('033a6dad-5a58-4b74-b420-6772bab3946e'), - $this->recurringPaymentToken->tokenIdentifier() - ); - } - - /** @test */ - public function it_contains_a_customer_id(): void - { - $this->assertEquals('YOUR_UNIQUE_SHOPPER_ID_IOfW3k9G2PvXFu2j', $this->recurringPaymentToken->customerId()); - } - - /** @test */ - public function it_contains_a_recurring_detail_reference(): void - { - $this->assertEquals('8415698462516992', $this->recurringPaymentToken->recurringDetailReference()); - } - - /** @test */ - public function it_contains_a_psp_reference(): void - { - $this->assertEquals('8515815919501547', $this->recurringPaymentToken->pspReference()); - } - - /** @test */ - public function it_contains_an_order_number(): void - { - $this->assertEquals('YOUR_ORDER_NUMBER', $this->recurringPaymentToken->orderNumber()); - } - - /** @test */ - public function it_contains_a_result_code_string(): void - { - $this->assertEquals('Authorised', $this->recurringPaymentToken->getResultCode()); - } - - /** @test */ - public function it_contains_a_result_code(): void - { - $this->assertEquals(PaymentResultCode::load('Authorised'), $this->recurringPaymentToken->resultCode()); - } - - /** @test */ - public function it_contains_an_amount_value(): void - { - $this->assertEquals(10500, $this->recurringPaymentToken->amountValue()); - } - - /** @test */ - public function it_contains_an_amount_currency(): void - { - $this->assertEquals('EUR', $this->recurringPaymentToken->amountCurrency()); - } - - /** @test */ - public function it_contains_a_created_at_timestamp(): void - { - $createdAt = new \DateTimeImmutable(); - $this->recurringPaymentToken->setCreatedAt($createdAt); - $this->assertInstanceOf(\DateTimeImmutable::class, $this->recurringPaymentToken->createdAt()); - $this->assertStringContainsString( - $createdAt->format('d/m/y H:i'), - $this->recurringPaymentToken->createdAt()->format('d/m/y H:i') - ); - } - - /** @test */ - public function it_contains_an_updated_at_timestamp(): void - { - $updatedAt = new \DateTimeImmutable(); - $this->assertInstanceOf(\DateTimeImmutable::class, $this->recurringPaymentToken->updatedAt()); - $this->assertStringContainsString( - $updatedAt->format('d/m/y'), - $this->recurringPaymentToken->updatedAt()->format('d/m/y') - ); - } - - /** @test */ - public function it_knows_when_it_is_a_one_off_payment(): void - { - $this->assertTrue($this->recurringPaymentToken->isOneOffPayment()); - } - - /** @test */ - public function it_knows_when_it_is_a_subscription(): void - { - $recurringPaymentTokenOrderNumberEmpty = RecurringPaymentToken::create( - TokenIdentifier::generateFromString($uuid = 'f958e8a5-c707-4901-91dd-0e16b22b898c'), - 'YOUR_UNIQUE_SHOPPER_ID_IOfW3k9G2PvXFu2j', - '8415698462516992', - '8515815919501547', - $orderNumber = '', - PaymentResultCode::authorised(), - 10500, - 'EUR' - ); - $this->assertTrue($recurringPaymentTokenOrderNumberEmpty->isSubscription()); - } -} diff --git a/tests/Unit/Models/RecurringPayment/RecurringProcessingModelTest.php b/tests/Unit/Models/RecurringPayment/RecurringProcessingModelTest.php deleted file mode 100755 index be7c7fef..00000000 --- a/tests/Unit/Models/RecurringPayment/RecurringProcessingModelTest.php +++ /dev/null @@ -1,74 +0,0 @@ -recurringProcessingModel = RecurringProcessingModel::cardOnFile(); - } - - /** @test */ - public function it_contains_a_recurring_processing_model(): void - { - $this->assertInstanceOf(RecurringProcessingModel::class, $this->recurringProcessingModel); - } - - /** @test */ - public function it_knows_when_it_equals_a_processing_model(): void - { - $this->assertTrue($this->recurringProcessingModel->equals(RecurringProcessingModel::cardOnFile())); - $this->assertFalse($this->recurringProcessingModel->equals(RecurringProcessingModel::subscription())); - } - - /** @test */ - public function it_knows_when_it_equals_a_recurring_processing_model(): void - { - $recurringProcessingModel = RecurringProcessingModel::cardOnFile(); - $this->assertEquals($this->recurringProcessingModel, $recurringProcessingModel); - $this->assertNotSame($this->recurringProcessingModel, $recurringProcessingModel); - } - - /** @test */ - public function it_throws_an_invalid_argument_exception_when_recurring_processing_model_is_unknown(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid recurring processing model: "test"'); - - RecurringProcessingModel::load('test'); - } - - /** @test */ - public function it_can_load_a_recurring_processing_model(): void - { - $this->assertEquals( - RecurringProcessingModel::cardOnFile(), - RecurringProcessingModel::load('CardOnFile') - ); - } - - /** - * @dataProvider recurringProcessingModelProvider - * @test - */ - public function it_contains_recurring_processing_model( - RecurringProcessingModel $recurringProcessingModel, string $expected - ): void { - $this->assertEquals($expected, $recurringProcessingModel->recurringProcessingModel()); - } - - public function recurringProcessingModelProvider(): \Generator - { - yield [RecurringProcessingModel::cardOnFile(), 'CardOnFile']; - yield [RecurringProcessingModel::subscription(), 'Subscription']; - } -} diff --git a/tests/Unit/Models/RecurringPayment/ShopperInteractionTest.php b/tests/Unit/Models/RecurringPayment/ShopperInteractionTest.php deleted file mode 100755 index c3487af6..00000000 --- a/tests/Unit/Models/RecurringPayment/ShopperInteractionTest.php +++ /dev/null @@ -1,71 +0,0 @@ -shopperInteraction = ShopperInteraction::contAuth(); - } - - /** @test */ - public function it_contains_a_shopper_interaction(): void - { - $this->assertInstanceOf(ShopperInteraction::class, $this->shopperInteraction); - } - - /** @test */ - public function it_knows_when_it_equals_a_shopper_interaction(): void - { - $this->assertTrue($this->shopperInteraction->equals(ShopperInteraction::contAuth())); - $this->assertFalse($this->shopperInteraction->equals(ShopperInteraction::ecommerce())); - } - - /** @test */ - public function it_is_immutable_constructed(): void - { - $shopperInteractionContAuth = ShopperInteraction::contAuth(); - $this->assertEquals($this->shopperInteraction, $shopperInteractionContAuth); - $this->assertNotSame($this->shopperInteraction, $shopperInteractionContAuth); - } - - /** - * @dataProvider shopperInteractionProvider - * @test - */ - public function it_can_be_constructed_with_named_constructors(ShopperInteraction $shopperInteraction, string $expected): void - { - $this->assertEquals($expected, $shopperInteraction->shopperInteraction()); - } - - public function shopperInteractionProvider(): \Generator - { - yield [ShopperInteraction::contAuth(), 'ContAuth']; - yield [ShopperInteraction::ecommerce(), 'Ecommerce']; - } - - /** @test */ - public function it_throws_an_invalid_argument_exception_when_shopper_interaction_is_unknown(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid shopper interaction: "test"'); - - ShopperInteraction::load('test'); - } - - /** @test */ - public function it_can_load_a_shopper_interaction(): void - { - $result = ShopperInteraction::ecommerce(); - $this->assertEquals(ShopperInteraction::ecommerce(), $result); - } -} diff --git a/tests/Unit/Models/TokenIdentifierTest.php b/tests/Unit/Models/TokenIdentifierTest.php deleted file mode 100755 index 1c0f6e15..00000000 --- a/tests/Unit/Models/TokenIdentifierTest.php +++ /dev/null @@ -1,56 +0,0 @@ -tokenIdentifier = TokenIdentifier::generateFromString('3a2ee0d3-adc0-4386-869d-429b6d5f1fa0'); - } - - /** @test */ - public function it_contains_a_token_identifier(): void - { - $this->assertInstanceOf(TokenIdentifier::class, $this->tokenIdentifier); - } - - /** @test */ - public function it_knows_when_it_equals_token_identifier_objects(): void - { - $this->assertTrue($this->tokenIdentifier->equals(TokenIdentifier::generateFromString('3a2ee0d3-adc0-4386-869d-429b6d5f1fa0'))); - $this->assertFalse($this->tokenIdentifier->equals(TokenIdentifier::generate())); - } - - /** @test */ - public function it_constructs_immutable(): void - { - $tokenIdentifier = TokenIdentifier::generateFromString('3a2ee0d3-adc0-4386-869d-429b6d5f1fa0'); - $this->assertEquals($this->tokenIdentifier, $tokenIdentifier); - $this->assertNotSame($this->tokenIdentifier, $tokenIdentifier); - } - - /** @test */ - public function it_can_be_constructed_from_string(): void - { - $tokenIdentifier = TokenIdentifier::generateFromString($expected = 'af55ecab-90db-4501-ba7d-9eef61ac3ee3'); - - $this->assertEquals($expected, $tokenIdentifier->identifier()); - } - - /** @test */ - public function it_can_be_constructed_with_named_constructor(): void - { - $tokenIdentifier = TokenIdentifier::generate(); - - $this->assertInstanceOf(TokenIdentifier::class, $tokenIdentifier); - } -} diff --git a/tests/Unit/Recurring/RecurringTokenFactoryTest.php b/tests/Unit/Recurring/RecurringTokenFactoryTest.php deleted file mode 100644 index 1f422b2f..00000000 --- a/tests/Unit/Recurring/RecurringTokenFactoryTest.php +++ /dev/null @@ -1,89 +0,0 @@ -recurringTokenMapper = new RecurringTokenFactory(); - } - - /** @test */ - public function it_is_a_recurring_token_mapper(): void - { - $this->assertInstanceOf(RecurringTokenFactoryInterface::class, $this->recurringTokenMapper); - } - - /** @test */ - public function it_throws_invalid_payments_response_exception(): void - { - $this->expectException(InvalidPaymentsResponseException::class); - $this->expectExceptionMessage('Empty Payment data.'); - - RecurringTokenFactory::create([]); - } - - /** @test */ - public function it_can_map_from_array(): void - { - $adyenPaymentsResponseArray = [ - 'additionalData' => [ - 'recurring.recurringDetailReference' => '8415698462516992', - 'recurring.shopperReference' => 'YOUR_UNIQUE_SHOPPER_ID_IOfW3k9G2PvXFu2j', - ], - 'pspReference' => '8515815919501547', - 'resultCode' => 'Authorised', - 'amount' => [ - 'currency' => 'USD', - 'value' => 0, - ], - 'merchantReference' => 'YOUR_ORDER_NUMBER', - ]; - $recurringPaymentToken = RecurringTokenFactory::create($adyenPaymentsResponseArray); - - $this->assertEquals('YOUR_UNIQUE_SHOPPER_ID_IOfW3k9G2PvXFu2j', $recurringPaymentToken->customerId()); - $this->assertEquals('8415698462516992', $recurringPaymentToken->recurringDetailReference()); - $this->assertEquals('8515815919501547', $recurringPaymentToken->pspReference()); - $this->assertEquals('YOUR_ORDER_NUMBER', $recurringPaymentToken->orderNumber()); - $this->assertEquals(PaymentResultCode::load('Authorised'), $recurringPaymentToken->resultCode()); - $this->assertIsInt($recurringPaymentToken->amountValue()); - $this->assertEquals(0, $recurringPaymentToken->amountValue()); - $this->assertEquals('USD', $recurringPaymentToken->amountCurrency()); - } - - /** @test */ - public function it_can_map_default_values(): void - { - $adyenPaymentsResponseArray = [ - 'additionalData' => [ - ], - 'amount' => [ - ], - ]; - $recurringPaymentToken = RecurringTokenFactory::create($adyenPaymentsResponseArray); - - $this->assertEquals('', $recurringPaymentToken->customerId()); - $this->assertEquals('', $recurringPaymentToken->recurringDetailReference()); - $this->assertEquals('', $recurringPaymentToken->pspReference()); - $this->assertEquals('', $recurringPaymentToken->orderNumber()); - $this->assertEquals(PaymentResultCode::load('Invalid'), $recurringPaymentToken->resultCode()); - $this->assertEquals(0, $recurringPaymentToken->amountValue()); - $this->assertEquals('', $recurringPaymentToken->amountCurrency()); - } -} diff --git a/tests/Unit/Repository/RecurringPayment/RecurringPaymentTokenRepositoryTest.php b/tests/Unit/Repository/RecurringPayment/RecurringPaymentTokenRepositoryTest.php deleted file mode 100755 index 97fbbb74..00000000 --- a/tests/Unit/Repository/RecurringPayment/RecurringPaymentTokenRepositoryTest.php +++ /dev/null @@ -1,113 +0,0 @@ -entityManager = $this->prophesize(EntityManager::class); - $this->recurringPaymentTokenEntityRepository = $this->prophesize(EntityRepository::class); - $this->recurringPaymentTokenRepository = new RecurringPaymentTokenRepository( - $this->entityManager->reveal(), - $this->recurringPaymentTokenEntityRepository->reveal() - ); - } - - /** @test */ - public function it_is_a_recurring_payment_token_repository(): void - { - $this->assertInstanceOf(RecurringPaymentTokenRepositoryInterface::class, $this->recurringPaymentTokenRepository); - } - - /** @test */ - public function it_can_fetch_a_recurring_payment_token_by_customer_id_and_order_number(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - - $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'customerId' => $customerId = 'customer-id', - 'orderNumber' => $orderNumber = 'order-number', - ])->willReturn($recurringPaymentToken->reveal()); - - $result = $this->recurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber($customerId, $orderNumber); - - self::assertEquals($recurringPaymentToken->reveal(), $result); - } - - /** @test */ - public function it_will_throw_an_error_on_missing_recurring_payment_token_by_customer_id_and_order_number(): void - { - $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'customerId' => $customerId = 'customer-id', - 'orderNumber' => $orderNumber = 'order-number', - ])->willReturn(null); - - self::expectException(RecurringPaymentTokenNotFoundException::class); - - $this->recurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber($customerId, $orderNumber); - } - - /** @test */ - public function it_can_fetch_a_recurring_payment_token_by_psp_reference(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - - $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'resultCode' => PaymentResultCode::pending()->resultCode(), - 'pspReference' => $pspReference = 'psp-reference', - ])->willReturn($recurringPaymentToken->reveal()); - - $result = $this->recurringPaymentTokenRepository->fetchPendingByPspReference($pspReference); - - self::assertEquals($recurringPaymentToken->reveal(), $result); - } - - /** @test */ - public function it_will_throw_an_error_on_missing_recurring_payment_token_by_psp_reference(): void - { - $this->recurringPaymentTokenEntityRepository->findOneBy([ - 'resultCode' => PaymentResultCode::pending()->resultCode(), - 'pspReference' => $pspReference = 'psp-reference', - ])->willReturn(null); - - self::expectException(RecurringPaymentTokenNotFoundException::class); - - $this->recurringPaymentTokenRepository->fetchPendingByPspReference($pspReference); - } - - /** @test */ - public function it_can_update_a_recurring_payment_token(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - $this->entityManager->persist($recurringPaymentToken->reveal())->shouldBeCalled(); - $this->entityManager->flush($recurringPaymentToken->reveal())->shouldBeCalled(); - - $this->recurringPaymentTokenRepository->update($recurringPaymentToken->reveal()); - } -} diff --git a/tests/Unit/Repository/RecurringPayment/TraceableRecurringPaymentTokenRepositoryTest.php b/tests/Unit/Repository/RecurringPayment/TraceableRecurringPaymentTokenRepositoryTest.php deleted file mode 100755 index 4f6a7804..00000000 --- a/tests/Unit/Repository/RecurringPayment/TraceableRecurringPaymentTokenRepositoryTest.php +++ /dev/null @@ -1,154 +0,0 @@ -recurringPaymentTokenRepository = $this->prophesize(RecurringPaymentTokenRepositoryInterface::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->traceableRecurringPaymentTokenRepository = new TraceableRecurringPaymentTokenRepository( - $this->recurringPaymentTokenRepository->reveal(), - $this->logger->reveal() - ); - } - - /** @test */ - public function it_is_a_recurring_payment_token_repository(): void - { - $this->assertInstanceOf( - RecurringPaymentTokenRepositoryInterface::class, - $this->traceableRecurringPaymentTokenRepository - ); - } - - /** @test */ - public function it_can_fetch_a_recurring_payment_token_by_customer_id_and_order_number(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - - $this->recurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber( - $customerId = 'customer-id', - $orderNumber = 'order-number' - )->willReturn($recurringPaymentToken->reveal()); - - $result = $this->traceableRecurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber( - $customerId, - $orderNumber - ); - - self::assertEquals($recurringPaymentToken->reveal(), $result); - } - - /** @test */ - public function it_will_throw_an_error_on_missing_recurring_payment_token_by_customer_id_and_order_number(): void - { - $exception = new RecurringPaymentTokenNotFoundException(); - $this->recurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber( - $customerId = 'customer-id', - $orderNumber = 'order-number' - )->willThrow($exception); - - $this->logger->info($exception->getMessage(), ['exception' => $exception])->shouldBeCalled(); - - self::expectException(RecurringPaymentTokenNotFoundException::class); - - $this->traceableRecurringPaymentTokenRepository->fetchByCustomerIdAndOrderNumber($customerId, $orderNumber); - } - - /** @test */ - public function it_can_fetch_a_recurring_payment_token_by_psp_reference(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - - $this->recurringPaymentTokenRepository->fetchPendingByPspReference($pspReference = 'psp-reference') - ->willReturn($recurringPaymentToken->reveal()); - - $result = $this->traceableRecurringPaymentTokenRepository->fetchPendingByPspReference($pspReference); - - self::assertEquals($recurringPaymentToken->reveal(), $result); - } - - /** @test */ - public function it_will_throw_an_error_on_missing_recurring_payment_token_by_psp_reference(): void - { - $exception = new RecurringPaymentTokenNotFoundException(); - $this->recurringPaymentTokenRepository->fetchPendingByPspReference($pspReference = 'psp-reference') - ->willThrow($exception); - - $this->logger->info($exception->getMessage(), ['exception' => $exception])->shouldBeCalled(); - - self::expectException(RecurringPaymentTokenNotFoundException::class); - - $this->traceableRecurringPaymentTokenRepository->fetchPendingByPspReference($pspReference); - } - - /** @test */ - public function it_can_update_a_recurring_payment_token(): void - { - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - $this->recurringPaymentTokenRepository->update($recurringPaymentToken->reveal())->shouldBeCalled(); - - $this->traceableRecurringPaymentTokenRepository->update($recurringPaymentToken->reveal()); - } - - /** @test */ - public function it_can_catch_a_orm_exception_on_updating_a_recurring_payment_token(): void - { - $ormException = new ORMException(); - $token = TokenIdentifier::generate(); - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - $recurringPaymentToken->tokenIdentifier()->willReturn($token); - - $this->recurringPaymentTokenRepository->update($recurringPaymentToken->reveal())->willThrow($ormException); - $this->logger->error($ormException->getMessage(), ['exception' => $ormException]); - - self::expectException(RecurringPaymentTokenNotSavedException::class); - - $this->traceableRecurringPaymentTokenRepository->update($recurringPaymentToken->reveal()); - } - - /** @test */ - public function it_can_catch_a_orm_invalid_argument_exception_on_updating_a_recurring_payment_token(): void - { - $ormException = new ORMInvalidArgumentException(); - $token = TokenIdentifier::generate(); - $recurringPaymentToken = $this->prophesize(RecurringPaymentToken::class); - $recurringPaymentToken->tokenIdentifier()->willReturn($token); - - $this->recurringPaymentTokenRepository->update($recurringPaymentToken->reveal())->willThrow($ormException); - $this->logger->error($ormException->getMessage(), ['exception' => $ormException]); - - self::expectException(RecurringPaymentTokenNotSavedException::class); - - $this->traceableRecurringPaymentTokenRepository->update($recurringPaymentToken->reveal()); - } -} diff --git a/tests/Unit/Session/CustomerNumberProviderTest.php b/tests/Unit/Session/CustomerNumberProviderTest.php deleted file mode 100755 index 1fff4421..00000000 --- a/tests/Unit/Session/CustomerNumberProviderTest.php +++ /dev/null @@ -1,89 +0,0 @@ -session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->modelManager = $this->prophesize(ModelManager::class); - $this->customerNumberProvider = new CustomerNumberProvider( - $this->session->reveal(), - $this->modelManager->reveal() - ); - } - - /** @test */ - public function it_is_a_customer_number_provider(): void - { - $this->assertInstanceOf(CustomerNumberProviderInterface::class, $this->customerNumberProvider); - } - - /** @test */ - public function it_provides_empty_string_when_no_user_id_in_session(): void - { - $this->session->get('sUserId')->shouldBeCalledOnce()->willReturn(null); - $this->modelManager->getRepository(Customer::class)->shouldNotBeCalled(); - $customerNumber = ($this->customerNumberProvider)(); - - $this->assertEquals('', $customerNumber); - } - - /** @test */ - public function it_provides_empty_string_when_no_customer_returned_from_repository(): void - { - $customerRepository = $this->prophesize(EntityRepository::class); - - $this->session->get('sUserId')->shouldBeCalledOnce()->willReturn($userId = '123'); - $this->modelManager->getRepository(Customer::class) - ->shouldBeCalledOnce() - ->willReturn($customerRepository->reveal()); - $customerRepository->find($userId)->willReturn(null); - - $customerNumber = ($this->customerNumberProvider)(); - $this->assertEquals('', $customerNumber); - } - - /** @test */ - public function it_provides_customer_number(): void - { - $customer = new Customer(); - $customer->setNumber($customerNumber = 'abc'); - - $customerRepository = $this->prophesize(EntityRepository::class); - - $this->session->get('sUserId')->shouldBeCalledOnce()->willReturn($customerNumber); - $this->modelManager->getRepository(Customer::class) - ->shouldBeCalledOnce() - ->willReturn($customerRepository->reveal()); - $customerRepository->find($customerNumber) - ->willReturn($customer); - - $customerNumberExpected = ($this->customerNumberProvider)(); - $this->assertEquals($customerNumberExpected, $customerNumber); - } -} diff --git a/tests/Unit/Shopware/Controllers/Frontend/CheckoutTest.php b/tests/Unit/Shopware/Controllers/Frontend/CheckoutTest.php deleted file mode 100644 index ee0d6835..00000000 --- a/tests/Unit/Shopware/Controllers/Frontend/CheckoutTest.php +++ /dev/null @@ -1,151 +0,0 @@ -admin = $this->prophesize(\sAdmin::class); - $this->basket = $this->prophesize(\sBasket::class); - $this->session = $this->prophesize(\Enlight_Components_Session_Namespace::class); - $this->container = $this->prophesize(Container::class); - $this->engine = $this->prophesize(Enlight_Template_Manager::class); - $this->view = new \Enlight_View_Default($this->engine->reveal()); - $this->checkoutController = new MockCheckout(); - } - - /** @test */ - public function it_is_csrf_get_protection_aware(): void - { - $this->assertInstanceOf(CSRFGetProtectionAware::class, $this->checkoutController); - } - - /** @test */ - public function it_returns_complete_basket_data_to_view(): void - { - $this->markTestIncomplete(); -// $this->view->setScope(['test']); -// $this->view->assign('sUserData', ['additional' => ['countryShipping' => null]]); - - $this->checkoutController->setAdyenMockProperties( - $this->admin->reveal(), - $this->basket->reveal(), - $this->session->reveal(), - $this->container->reveal() -// $this->view - ); - - $countryList = [ $country = [ - "id" => 2, - "name" => "Deutschland", - "iso" => "DE", - "en" => "GERMANY", - "description" => "", - "position" => 1, - "active" => true, - "iso3" => "DEU", - "taxFree" => false, - "taxFreeForVatId" => false, - "vatIdCheck" => false, - "displayStateSelection" => false, - "requiresStateSelection" => false, - "allowShipping" => true, - "states" => [], - "areaId" => 1, - "attributes" => [], - "countryname" => "Deutschland", - "countryiso" => "DE", - "countryen" => "GERMANY", - "taxfree" => false, - "taxfree_ustid" => false, - "taxfree_ustid_checked" => false, - "display_state_in_registration" => false, - "force_state_in_registration" => false, - "areaID" => 1, - "allow_shipping" => true, - "flag" => false - ]]; - - $this->admin->sGetCountryList()->willReturn($countryList); -// $this->admin->sGetCountryList()->shouldBeCalledOnce(); - - $shop = new Shop(); - $currency = new Currency(); - $currency->setCurrency('EUR'); - $currency->setFactor(2); - $shop->setCurrency($currency); - $shop = $this->container->get('shop')->willReturn($shop); - - $positions = [ - new Price( - (float) $price = ($endPrice = 121.00) * ($quantity = 2), - (float) $netPrice = ($netPrice = 100.00 * $quantity), - (float) $taxRate = 21.0, - null - ) - ]; - $basketHelper = $this->prophesize(BasketHelperInterface::class); - $basketHelper->getPositionPrices(Argument::cetera())->willReturn($positions); - $positions = $this->container->get(BasketHelperInterface::class)->willReturn($basketHelper); - - $proportionalTaxCalculator = $this->prophesize(ProportionalTaxCalculatorInterface::class); - $taxCalculator = $this->container->get('shopware.cart.proportional_tax_calculator') - ->willReturn($proportionalTaxCalculator); - $proportionalTaxCalculator->hasDifferentTaxes(Argument::cetera()) - ->willReturn(true); - - $configComponent = $this->prophesize(\Shopware_Components_Config::class); - $config = $this->container->get(\Shopware_Components_Config::class) - ->willReturn($configComponent); - - $basketResult = $this->checkoutController->getBasket(); - } -} - -class MockCheckout extends \Shopware_Controllers_Frontend_Checkout -{ - - public function setAdyenMockProperties($admin, $basket, $session, $container, $view): void - { - $this->admin = $admin; - $this->basket = $basket; - $this->session = $session; - $this->container = $container; - $this->view = $view; - } -} diff --git a/tests/Unit/Shopware/Plugin/PluginIdProviderTest.php b/tests/Unit/Shopware/Plugin/PluginIdProviderTest.php deleted file mode 100644 index c51dfef2..00000000 --- a/tests/Unit/Shopware/Plugin/PluginIdProviderTest.php +++ /dev/null @@ -1,61 +0,0 @@ -pluginManager = $this->prophesize(InstallerService::class); - $this->logger = $this->prophesize(LoggerInterface::class); - $this->provider = new TraceablePluginIdProvider( - $this->pluginManager->reveal(), - $this->logger->reveal() - ); - } - - /** @test */ - public function it_can_provide_plugin_id(): void - { - $plugin = new Plugin(); - $plugin->setId($id = 3633); - $this->pluginManager->getPluginByName('AdyenPayment')->willReturn($plugin); - $this->logger->critical(Argument::cetera())->shouldNotBeCalled(); - - $result = $this->provider->provideId(); - $this->assertEquals($id, $result); - } - - /** @test */ - public function it_logs_and_throws_exception(): void - { - $this->pluginManager->getPluginByName(Argument::cetera()) - ->willThrow($exception = new \Exception($message = 'Some Unknown plugin')); - $this->logger->critical( - 'Could not provide the "id" of plugin "'.AdyenPayment::NAME.'"', - ['exception' => $exception] - )->shouldBeCalled(); - - $this->expectExceptionObject($exception); - $this->provider->provideId(); - } -} diff --git a/tests/Unit/Shopware/Serializer/SwPaymentMeanSerializerTest.php b/tests/Unit/Shopware/Serializer/SwPaymentMeanSerializerTest.php deleted file mode 100755 index e2bfca20..00000000 --- a/tests/Unit/Shopware/Serializer/SwPaymentMeanSerializerTest.php +++ /dev/null @@ -1,65 +0,0 @@ -serializer = new SwPaymentMeanSerializer(); - } - - /** @test */ - public function it_is_a_payment_mean_collection_serializer(): void - { - $this->assertInstanceOf(PaymentMeanSerializer::class, $this->serializer); - } - - /** @test */ - public function it_can_serialize(): void - { - $paymentMean = PaymentMean::createFromShopwareArray($raw = [ - 'id' => $id = 15, - 'source' => null, - 'name' => 'invoice', - 'description' => 'Rechnung', - 'additionaldescription' => 'additional', - ]); - - $result = ($this->serializer)($paymentMean); - $this->assertEquals([$id => $raw], $result); - } - - /** @test */ - public function it_can_serialize_html(): void - { - $paymentMean = PaymentMean::createFromShopwareArray($raw = [ - 'id' => $id = 7845, - 'source' => $source = 1, - 'name' => ' a name "quoted"', - 'description' => "description anda link", - 'additionaldescription' => "additional
a div
and link", - ]); - - $result = ($this->serializer)($paymentMean); - $this->assertEquals([ - $id => [ - 'id' => $id, - 'source' => $source, - 'name' => ' a name "quoted"', - 'description' => "description anda link", - 'additionaldescription' => "additional
a div
and link", - ], - ], $result); - } -} diff --git a/tests/Unit/Subscriber/Account/SaveStoredMethodPreferenceSubscriberTest.php b/tests/Unit/Subscriber/Account/SaveStoredMethodPreferenceSubscriberTest.php deleted file mode 100755 index fdfe58c5..00000000 --- a/tests/Unit/Subscriber/Account/SaveStoredMethodPreferenceSubscriberTest.php +++ /dev/null @@ -1,205 +0,0 @@ -args = $this->prophesize(Enlight_Controller_ActionEventArgs::class); - $this->request = $this->prophesize(Enlight_Controller_Request_Request::class); - $this->session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->userPreferenceManager = $this->prophesize(UserPreferenceManagerInterface::class); - $this->userPreferenceRepository = $this->prophesize(EntityRepository::class); - $this->storedPaymentMeanProvider = $this->prophesize(StoredPaymentMeanProviderInterface::class); - - $this->subscriber = new SaveStoredMethodPreferenceSubscriber( - $this->session->reveal(), - $this->userPreferenceManager->reveal(), - $this->userPreferenceRepository->reveal(), - $this->storedPaymentMeanProvider->reveal() - ); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - ['Enlight_Controller_Action_PostDispatch_Frontend_Account' => '__invoke'], - SaveStoredMethodPreferenceSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_missing_user_id(): void - { - $this->session->get('sUserId')->willReturn(null); - $this->request->getActionName()->shouldNotBeCalled(); - $this->request->isPost()->shouldNotBeCalled(); - $this->args->getRequest()->shouldNotBeCalled(); - $this->storedPaymentMeanProvider->fromRequest(Argument::cetera())->shouldNotBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_action_name(): void - { - $this->session->get('sUserId')->willReturn(123456); - $this->request->getActionName()->willReturn('wrong-action-name'); - $this->request->isPost()->willReturn(true); - $this->args->getRequest()->willReturn($this->request); - $this->storedPaymentMeanProvider->fromRequest(Argument::cetera())->shouldNotBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_method(): void - { - $this->session->get('sUserId')->willReturn(123456); - $this->request->getActionName()->willReturn('savePayment'); - $this->request->isPost()->willReturn(false); - $this->args->getRequest()->willReturn($this->request); - $this->storedPaymentMeanProvider->fromRequest(Argument::cetera())->shouldNotBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_will_save_the_user_preferences_with_empty_params(): void - { - $this->session->get('sUserId')->willReturn($userId = 123456); - $this->request->getActionName()->willReturn('savePayment'); - $this->request->isPost()->willReturn(true); - $this->request->getParam('register', [])->willReturn([]); - $this->args->getRequest()->willReturn($this->request->reveal()); - $this->storedPaymentMeanProvider->fromRequest($this->request->reveal())->willReturn(null); - - $userPreference = new UserPreference(); - $userPreference->setUserId($userId); - $userPreference->setStoredMethodId(null); - - $this->userPreferenceManager->save($userPreference)->shouldBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_will_save_the_user_preferences_with_null_for_none_stored_method_param(): void - { - $this->session->get('sUserId')->willReturn($userId = 123456); - $this->request->getActionName()->willReturn('savePayment'); - $this->request->isPost()->willReturn(true); - $this->request->getParam('register', [])->willReturn(['payment' => 'noneStoredPaymentId']); - $this->args->getRequest()->willReturn($this->request->reveal()); - $this->storedPaymentMeanProvider->fromRequest($this->request->reveal())->willReturn(null); - - $userPreference = new UserPreference(); - $userPreference->setUserId($userId); - $userPreference->setStoredMethodId(null); - - $this->userPreferenceManager->save($userPreference)->shouldBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_will_save_the_user_preferences_with_param_value(): void - { - $this->session->get('sUserId')->willReturn($userId = 123456); - $this->request->getActionName()->willReturn('savePayment'); - $this->request->isPost()->willReturn(true); - $this->request->getParam('register', [])->willReturn([ - 'payment' => 'proper_'.($storedMethodId = 'storedMethodId'), - ]); - $this->args->getRequest()->willReturn($this->request->reveal()); - $storedPaymentMean = PaymentMean::createFromShopwareArray([ - 'source' => 'any', - 'stored_method_id' => $storedMethodId, - ]); - $this->storedPaymentMeanProvider->fromRequest($this->request->reveal())->willReturn($storedPaymentMean); - - $userPreference = new UserPreference(); - $userPreference->setUserId($userId); - $userPreference->setStoredMethodId($storedMethodId); - - $this->userPreferenceManager->save($userPreference)->shouldBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } - - /** @test */ - public function it_will_update_the_user_preferences_with_param_value(): void - { - $this->session->get('sUserId')->willReturn($userId = 123456); - $this->request->getActionName()->willReturn('savePayment'); - $this->request->isPost()->willReturn(true); - $this->request->getParam('register', [])->willReturn([ - 'payment' => 'proper_'.($storedMethodId = 'storedMethodId'), - ]); - $this->args->getRequest()->willReturn($this->request->reveal()); - $storedPaymentMean = PaymentMean::createFromShopwareArray([ - 'source' => 'any', - 'stored_method_id' => $storedMethodId, - ]); - $this->storedPaymentMeanProvider->fromRequest($this->request->reveal())->willReturn($storedPaymentMean); - - $userPreference = new UserPreference(); - $userPreference->setId(123); - $userPreference->setUserId($userId); - $userPreference->setStoredMethodId($storedMethodId); - - $this->userPreferenceRepository->findOneBy(['userId' => $userId])->willReturn($userPreference); - - $this->userPreferenceManager->save($userPreference)->shouldBeCalled(); - - $this->subscriber->__invoke($this->args->reveal()); - } -} diff --git a/tests/Unit/Subscriber/Backend/HideStoredPaymentsSubscriberTest.php b/tests/Unit/Subscriber/Backend/HideStoredPaymentsSubscriberTest.php deleted file mode 100755 index 1c7182b7..00000000 --- a/tests/Unit/Subscriber/Backend/HideStoredPaymentsSubscriberTest.php +++ /dev/null @@ -1,119 +0,0 @@ -subscriber = new HideStoredPaymentsSubscriber(); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - [ - 'Enlight_Controller_Action_PostDispatchSecure_Backend_Payment' => '__invoke', - ], - HideStoredPaymentsSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_missing_request(): void - { - $eventArgs = new \Enlight_Controller_ActionEventArgs([ - 'subject' => $this->buildSubject($viewData = ['data' => 'view-data']), - 'request' => null, - 'response' => new \Enlight_Controller_Response_ResponseTestCase(), - ]); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_response(): void - { - $eventArgs = new \Enlight_Controller_ActionEventArgs([ - 'subject' => $this->buildSubject($viewData = ['data' => 'view-data']), - 'request' => new \Enlight_Controller_Request_RequestTestCase(), - 'response' => null, - ]); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_non_success_response_code(): void - { - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data'], Response::HTTP_BAD_REQUEST); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_action_name(): void - { - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_empty_view_data(): void - { - $eventArgs = $this->buildEventArgs('getPayments', $viewData = []); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_filters_hidden_payment_means(): void - { - $viewData = [ - 'data' => [ - $nonHiddenPaymentMean = [ - 'name' => 'A payment mean not hidden', - 'source' => SourceType::shopwareDefault()->getType(), - 'hide' => false, - ], - [ - 'name' => 'A hidden payment mean', - 'source' => SourceType::shopwareDefault()->getType(), - 'hide' => true, - ], - ], - ]; - $eventArgs = $this->buildEventArgs('getPayments', $viewData); - - $this->subscriber->__invoke($eventArgs); - $this->assertEquals([ - 'data' => [ - $nonHiddenPaymentMean, - ], - ], $eventArgs->getSubject()->View()->getAssign()); - } -} diff --git a/tests/Unit/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriberTest.php b/tests/Unit/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriberTest.php deleted file mode 100755 index 99b477d1..00000000 --- a/tests/Unit/Subscriber/Checkout/EnrichUmbrellaPaymentMeanSubscriberTest.php +++ /dev/null @@ -1,176 +0,0 @@ -session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->paymentMeansProvider = $this->prophesize(PaymentMeansProviderInterface::class); - $this->subscriber = new EnrichUmbrellaPaymentMeanSubscriber( - $this->session->reveal(), - $this->paymentMeansProvider->reveal() - ); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - ['Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke'], - EnrichUmbrellaPaymentMeanSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_action_name(): void - { - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_xhr_request(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = ['data' => 'view-data']); - $eventArgs->getRequest()->setParam('isXHR', true); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_session_and_none_preselected_stored_method_id(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = ['data' => 'view-data']); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - $this->paymentMeansProvider->__invoke()->willReturn([]); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_umbrella_method_for_preselected_payment(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = [ - 'sUserData' => ['additional' => ['payment' => ['id' => 'preselectedPaymentId']]], - ]); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - $this->paymentMeansProvider->__invoke()->willReturn([]); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_payment_mean_for_stored_method(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = ['data' => 'view-data']); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->paymentMeansProvider->__invoke()->willReturn([]); - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn($storedMethodId = 'method-id'); - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_use_the_preselected_stored_method_id_on_preselected_umbrella_payment(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = [ - 'adyenUserPreference' => ['storedMethodId' => $preselectedStoredMethod = 'any-stored-method-id'], - 'sUserData' => ['additional' => ['payment' => ['id' => $preselectedUmbrellaId = 'umbrellaPaymentId']]], - 'sFormData' => [], - ]); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - $this->paymentMeansProvider->__invoke()->willReturn([ - $umbrellaPaymentMeanRaw = [ - 'id' => $preselectedUmbrellaId, - 'name' => AdyenPayment::ADYEN_STORED_PAYMENT_UMBRELLA_CODE, - 'source' => 123, - 'adyenType' => 'test', - ], - $paymentMeanRaw = [ - 'source' => 1234, - 'adyenType' => 'test', - 'stored_method_id' => $preselectedStoredMethod, - 'stored_method_umbrella_id' => $umbrellaId = 'umbrella-id', - ], - ]); - - $this->subscriber->__invoke($eventArgs); - - $expected = [ - 'adyenUserPreference' => ['storedMethodId' => $preselectedStoredMethod], - 'sUserData' => ['additional' => ['payment' => $paymentMeanRaw]], - 'sFormData' => ['payment' => $umbrellaId], - ]; - - self::assertEquals($expected, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_will_enrich_the_payment_mean_for_stored_method(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = [ - 'sUserData' => ['additional' => ['payment' => ['not-enriched-payment-data']]], - 'sFormData' => [], - ]); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn($storedMethodId = 'method-id'); - $this->paymentMeansProvider->__invoke()->willReturn([$paymentMeanRaw = [ - 'source' => 123, - 'adyenType' => 'test', - 'stored_method_id' => $storedMethodId, - 'stored_method_umbrella_id' => $umbrellaId = 'umbrella-id', - ]]); - - $this->subscriber->__invoke($eventArgs); - - $expected = [ - 'sUserData' => ['additional' => ['payment' => $paymentMeanRaw]], - 'sFormData' => ['payment' => $umbrellaId], - ]; - - self::assertEquals($expected, $eventArgs->getSubject()->View()->getAssign()); - } -} diff --git a/tests/Unit/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriberTest.php b/tests/Unit/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriberTest.php deleted file mode 100755 index 6f7725f5..00000000 --- a/tests/Unit/Subscriber/Checkout/EnrichUserAdditionalPaymentSubscriberTest.php +++ /dev/null @@ -1,170 +0,0 @@ -enrichedPaymentMeanProvider = $this->prophesize(EnrichedPaymentMeanProviderInterface::class); - $this->paymentMeansProvider = $this->prophesize(PaymentMeansProviderInterface::class); - $this->session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->subscriber = new EnrichUserAdditionalPaymentSubscriber( - $this->enrichedPaymentMeanProvider->reveal(), - $this->paymentMeansProvider->reveal(), - $this->session->reveal() - ); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - ['Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => ['__invoke', -99999]], - EnrichUserAdditionalPaymentSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_action_name(): void - { - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_stored_method_id_and_payment_mean_id(): void - { - $eventArgs = $this->buildEventArgs('confirm', $viewData = ['sUserData' => []]); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - - $this->subscriber->__invoke($eventArgs); - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_payment_mean_for_stored_method_id(): void - { - $eventArgs = $this->buildEventArgs('confirm', $viewData = ['sUserData' => []]); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn('method-id'); - $this->paymentMeansProvider->__invoke()->willReturn($paymentMeansRaw = [[ - 'source' => 123, - 'adyenType' => 'test', - ]]); - - $paymentMeans = PaymentMeanCollection::createFromShopwareArray($paymentMeansRaw); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willReturn($paymentMeans); - - $this->subscriber->__invoke($eventArgs); - - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_payment_mean_for_payment_id(): void - { - $eventArgs = $this->buildEventArgs('confirm', $viewData = ['sUserData' => [ - 'additional' => ['payment' => ['id' => $paymentId = '123123']], - ]]); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - $this->paymentMeansProvider->__invoke()->willReturn($paymentMeansRaw = [[ - 'source' => 123, - 'adyenType' => 'test', - ]]); - - $paymentMeans = PaymentMeanCollection::createFromShopwareArray($paymentMeansRaw); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willReturn($paymentMeans); - - $this->subscriber->__invoke($eventArgs); - - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_will_update_the_view_with_payment_mean_for_stored_method_id(): void - { - $eventArgs = $this->buildEventArgs('confirm', $viewData = ['sUserData' => []]); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn($storedMethodId = 'method-id'); - $this->paymentMeansProvider->__invoke()->willReturn($paymentMeansRaw = [$paymentMeanRaw = [ - 'source' => 123, - 'adyenType' => 'test', - 'stored_method_id' => $storedMethodId, - ]]); - - $paymentMeans = PaymentMeanCollection::createFromShopwareArray($paymentMeansRaw); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willReturn($paymentMeans); - - $this->subscriber->__invoke($eventArgs); - - $expected = [ - 'sUserData' => ['additional' => ['payment' => $paymentMeanRaw]], - ]; - - self::assertEquals($expected, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_will_update_the_view_with_payment_mean_for_payment_id(): void - { - $eventArgs = $this->buildEventArgs('confirm', $viewData = ['sUserData' => [ - 'additional' => ['payment' => ['id' => $paymentId = '123123']], - ]]); - - $this->session->get(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID)->willReturn(null); - $this->paymentMeansProvider->__invoke()->willReturn($paymentMeansRaw = [$paymentMeanRaw = [ - 'id' => $paymentId, - 'source' => 123, - 'adyenType' => 'test', - ]]); - - $paymentMeans = PaymentMeanCollection::createFromShopwareArray($paymentMeansRaw); - $this->enrichedPaymentMeanProvider->__invoke($paymentMeans)->willReturn($paymentMeans); - - $this->subscriber->__invoke($eventArgs); - - $expected = [ - 'sUserData' => ['additional' => ['payment' => $paymentMeanRaw]], - ]; - - self::assertEquals($expected, $eventArgs->getSubject()->View()->getAssign()); - } -} diff --git a/tests/Unit/Subscriber/Checkout/PersistStoredMehtodIdSubscriberTest.php b/tests/Unit/Subscriber/Checkout/PersistStoredMehtodIdSubscriberTest.php deleted file mode 100755 index b6431020..00000000 --- a/tests/Unit/Subscriber/Checkout/PersistStoredMehtodIdSubscriberTest.php +++ /dev/null @@ -1,99 +0,0 @@ -session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->modules = $this->prophesize(Shopware_Components_Modules::class); - $this->modules->Admin()->willReturn($this->prophesize(sAdmin::class)); - $this->subscriber = new PersistStoredMethodIdSubscriber($this->session->reveal(), $this->modules->reveal()); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - ['Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => '__invoke'], - PersistStoredMethodIdSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_wrong_request_action_name(): void - { - $eventArgs = $this->buildEventArgs('', $viewData = []); - $eventArgs->getRequest()->setParam('isXHR', true); - - $this->session->set(Argument::cetera())->shouldNotBeCalled(); - - $this->subscriber->__invoke($eventArgs); - } - - /** @test */ - public function it_does_nothing_on_shipping_payment_non_xhr_request(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = []); - $eventArgs->getRequest()->setParam('isXHR', false); - - $this->session->set(Argument::cetera())->shouldNotBeCalled(); - - $this->subscriber->__invoke($eventArgs); - } - - /** @test */ - public function it_saves_in_session_the_stored_method_id_on_shipping_payment_xhr_request(): void - { - $eventArgs = $this->buildEventArgs('shippingPayment', $viewData = []); - $eventArgs->getRequest()->setParam('isXHR', true); - $eventArgs->getRequest()->setParam(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, $storedMethodId = '123123'); - - $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, $storedMethodId)->shouldBeCalled(); - - $this->subscriber->__invoke($eventArgs); - } - - /** @test */ - public function it_saves_in_session_the_stored_method_id_on_save_shipping_payment(): void - { - $eventArgs = $this->buildEventArgs('saveShippingPayment', $viewData = []); - $eventArgs->getRequest()->setParam('isXHR', false); - $eventArgs->getRequest()->setParam(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, $storedMethodId = '123123'); - - $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_STORED_METHOD_ID, $storedMethodId)->shouldBeCalled(); - - $this->subscriber->__invoke($eventArgs); - } -} diff --git a/tests/Unit/Subscriber/EnrichUserPreferenceSubscriberTest.php b/tests/Unit/Subscriber/EnrichUserPreferenceSubscriberTest.php deleted file mode 100755 index 9f9d53b0..00000000 --- a/tests/Unit/Subscriber/EnrichUserPreferenceSubscriberTest.php +++ /dev/null @@ -1,107 +0,0 @@ -session = $this->prophesize(Enlight_Components_Session_Namespace::class); - $this->userPreferenceRepository = $this->prophesize(EntityRepository::class); - $this->subscriber = new EnrichUserPreferenceSubscriber( - $this->session->reveal(), - $this->userPreferenceRepository->reveal() - ); - } - - /** @test */ - public function it_is_a_subscriber(): void - { - self::assertInstanceOf(SubscriberInterface::class, $this->subscriber); - } - - /** @test */ - public function it_subscribe_to_the_proper_events(): void - { - self::assertEquals( - [ - // inject in the view as early as possible to get the info in the other subscribers - 'Enlight_Controller_Action_PostDispatch_Frontend_Account' => ['__invoke', -99999], - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => ['__invoke', -99999], - ], - EnrichUserPreferenceSubscriber::getSubscribedEvents() - ); - } - - /** @test */ - public function it_does_nothing_on_missing_user_id(): void - { - $this->session->get('sUserId')->willReturn(null); - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_does_nothing_on_missing_user_preference(): void - { - $this->session->get('sUserId')->willReturn($userId = 1234); - $this->userPreferenceRepository->findOneBy(['userId' => $userId])->willReturn(null); - - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - - self::assertEquals($viewData, $eventArgs->getSubject()->View()->getAssign()); - } - - /** @test */ - public function it_will_enrich_the_view_with_the_user_preference(): void - { - $this->session->get('sUserId')->willReturn($userId = 1234); - - $userPreference = new UserPreference(); - $userPreference->setId($id = 123123123); - $userPreference->setUserId($userId); - $userPreference->setStoredMethodId($storedMethodId = 'storedMethodId'); - $this->userPreferenceRepository->findOneBy(['userId' => $userId])->willReturn($userPreference); - - $eventArgs = $this->buildEventArgs('', $viewData = ['data' => 'view-data']); - - $this->subscriber->__invoke($eventArgs); - - $expected = [ - 'data' => 'view-data', - 'adyenUserPreference' => [ - 'id' => $id, - 'userId' => $userId, - 'storedMethodId' => $storedMethodId, - ], - ]; - self::assertEquals($expected, $eventArgs->getSubject()->View()->getAssign()); - } -} diff --git a/tests/Unit/Subscriber/SubscriberTestCase.php b/tests/Unit/Subscriber/SubscriberTestCase.php deleted file mode 100644 index 4a650e36..00000000 --- a/tests/Unit/Subscriber/SubscriberTestCase.php +++ /dev/null @@ -1,42 +0,0 @@ -setActionName($actionName); - $request->setMethod($requestMethod); - - return new \Enlight_Controller_ActionEventArgs([ - 'subject' => $this->buildSubject($viewData), - 'request' => $request, - 'response' => new \Enlight_Controller_Response_ResponseTestCase('', $status), - ]); - } - - protected function buildSubject(array $viewData): \Enlight_Controller_Action - { - Assert::allString(array_keys($viewData)); - - $subject = new ControllerActionMock(); - $subject->setView(new \Enlight_View_Default(new \Enlight_Template_Manager())); - $subject->View()->assign($viewData); - - return $subject; - } -} diff --git a/tests/Unit/Utils/SanitizeTest.php b/tests/Unit/Utils/SanitizeTest.php deleted file mode 100644 index 30c529e5..00000000 --- a/tests/Unit/Utils/SanitizeTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertEquals('This_is_a_1st_test', $result); - } - - /** @test */ - public function it_can_escape_without_quotes(): void - { - $result = Sanitize::escape("Test"); - - $this->assertEquals("<a href='test'>Test</a>", $result); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 09e3ac45..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ -=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.4.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "AWS SDK Common Runtime Team", - "email": "aws-sdk-common-runtime@amazon.com" - } - ], - "description": "AWS Common Runtime for PHP", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "crt", - "sdk" - ], - "support": { - "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" - }, - "time": "2021-09-03T22:57:30+00:00" - }, - { - "name": "aws/aws-sdk-php", - "version": "3.258.1", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7d7c4f89d2d0bd77c36cb8f3c8cd20b5aa8c0e6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7d7c4f89d2d0bd77c36cb8f3c8cd20b5aa8c0e6d", - "reference": "7d7c4f89d2d0bd77c36cb8f3c8cd20b5aa8c0e6d", - "shasum": "" - }, - "require": { - "aws/aws-crt-php": "^1.0.2", - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.8.5 || ^2.3", - "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "composer/composer": "^1.10.22", - "dms/phpunit-arraysubset-asserts": "^0.4.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "nette/neon": "^2.3", - "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3 || ^4.0", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Aws\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "support": { - "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", - "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.258.1" - }, - "time": "2023-02-01T19:22:26+00:00" - }, - { - "name": "bcremer/line-reader", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/bcremer/LineReader.git", - "reference": "568aae7a35a73e9ae6a6e2063e6f6760208006f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bcremer/LineReader/zipball/568aae7a35a73e9ae6a6e2063e6f6760208006f2", - "reference": "568aae7a35a73e9ae6a6e2063e6f6760208006f2", - "shasum": "" - }, - "require": { - "php": "^7.3|^7.4|^8.0|^8.1" - }, - "require-dev": { - "infection/infection": "^0.18", - "phpunit/phpunit": "^9.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Bcremer\\LineReader\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Benjamin Cremer", - "email": "bc@benjamin-cremer.de" - } - ], - "description": "Read large files line by line in a memory efficient (constant) way.", - "support": { - "issues": "https://github.com/bcremer/LineReader/issues", - "source": "https://github.com/bcremer/LineReader/tree/1.2.0" - }, - "time": "2021-10-13T16:06:27+00:00" - }, - { - "name": "beberlei/assert", - "version": "v3.3.2", - "source": { - "type": "git", - "url": "https://github.com/beberlei/assert.git", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "^7.0 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": ">=6.0.0", - "yoast/phpunit-polyfills": "^0.1.0" - }, - "suggest": { - "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" - }, - "type": "library", - "autoload": { - "files": [ - "lib/Assert/functions.php" - ], - "psr-4": { - "Assert\\": "lib/Assert" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de", - "role": "Lead Developer" - }, - { - "name": "Richard Quadling", - "email": "rquadling@gmail.com", - "role": "Collaborator" - } - ], - "description": "Thin assertion library for input validation in business models.", - "keywords": [ - "assert", - "assertion", - "validation" - ], - "support": { - "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" - }, - "time": "2021-12-16T21:41:27+00:00" - }, - { - "name": "beberlei/doctrineextensions", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/beberlei/DoctrineExtensions.git", - "reference": "008f162f191584a6c37c03a803f718802ba9dd9a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/008f162f191584a6c37c03a803f718802ba9dd9a", - "reference": "008f162f191584a6c37c03a803f718802ba9dd9a", - "shasum": "" - }, - "require": { - "doctrine/orm": "^2.7", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "nesbot/carbon": "*", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "symfony/yaml": "^4.2 || ^5.0", - "zf1/zend-date": "^1.12", - "zf1/zend-registry": "^1.12" - }, - "type": "library", - "autoload": { - "psr-4": { - "DoctrineExtensions\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Steve Lacey", - "email": "steve@steve.ly" - } - ], - "description": "A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL, Oracle, PostgreSQL and SQLite.", - "keywords": [ - "database", - "doctrine", - "orm" - ], - "support": { - "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.3.0" - }, - "time": "2020-11-29T07:37:23+00:00" - }, - { - "name": "brick/math", - "version": "0.9.3", - "source": { - "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.9.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brick\\Math\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Arbitrary-precision arithmetic library", - "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "brick", - "math" - ], - "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.3" - }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" - } - ], - "time": "2021-08-15T20:50:18+00:00" - }, - { - "name": "cocur/slugify", - "version": "v4.2.0", - "source": { - "type": "git", - "url": "https://github.com/cocur/slugify.git", - "reference": "7e7d03067d1075b1147090b3e1df672dfffb9dc3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cocur/slugify/zipball/7e7d03067d1075b1147090b3e1df672dfffb9dc3", - "reference": "7e7d03067d1075b1147090b3e1df672dfffb9dc3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": "^7.1 || ~8.0.0 || ~8.1.0" - }, - "conflict": { - "symfony/config": "<3.4 || >=4,<4.3", - "symfony/dependency-injection": "<3.4 || >=4,<4.3", - "symfony/http-kernel": "<3.4 || >=4,<4.3", - "twig/twig": "<2.12.1" - }, - "require-dev": { - "laravel/framework": "^5.0|^6.0|^7.0|^8.0", - "latte/latte": "~2.2", - "league/container": "^2.2.0", - "mikey179/vfsstream": "~1.6.8", - "mockery/mockery": "^1.3", - "nette/di": "~2.4", - "pimple/pimple": "~1.1", - "plumphp/plum": "~0.1", - "symfony/config": "^3.4 || ^4.3 || ^5.0 || ^6.0", - "symfony/dependency-injection": "^3.4 || ^4.3 || ^5.0 || ^6.0", - "symfony/http-kernel": "^3.4 || ^4.3 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4 || ^6.0", - "twig/twig": "^2.12.1 || ~3.0", - "zendframework/zend-modulemanager": "~2.2", - "zendframework/zend-servicemanager": "~2.2", - "zendframework/zend-view": "~2.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Cocur\\Slugify\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florian Eckerstorfer", - "email": "florian@eckerstorfer.co", - "homepage": "https://florian.ec" - }, - { - "name": "Ivo Bathke", - "email": "ivo.bathke@gmail.com" - } - ], - "description": "Converts a string into a slug.", - "keywords": [ - "slug", - "slugify" - ], - "support": { - "issues": "https://github.com/cocur/slugify/issues", - "source": "https://github.com/cocur/slugify/tree/v4.2.0" - }, - "time": "2022-08-13T15:23:32+00:00" - }, - { - "name": "composer/pcre", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2", - "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/1.0.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-12-06T15:17:27+00:00" - }, - { - "name": "composer/semver", - "version": "3.2.6", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "83e511e247de329283478496f7a1e114c9517506" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", - "reference": "83e511e247de329283478496f7a1e114c9517506", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.54", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.6" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-10-25T11:34:17+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6555461e76962fd0379c444c46fd558a0fcfb65e", - "reference": "6555461e76962fd0379c444c46fd558a0fcfb65e", - "shasum": "" - }, - "require": { - "composer/pcre": "^1", - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-12-08T13:07:32+00:00" - }, - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.1", - "source": { - "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", - "reference": "fe390591e0241955f22eb9ba327d137e501c771c", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "phpcompatibility/php-compatibility": "^9.0", - "sensiolabs/security-checker": "^4.1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" - }, - "time": "2020-12-07T18:04:37+00:00" - }, - { - "name": "doctrine/annotations", - "version": "1.13.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" - }, - "time": "2022-07-02T10:48:51+00:00" - }, - { - "name": "doctrine/cache", - "version": "1.13.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "56cd022adb5514472cb144c087393c1821911d09" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/56cd022adb5514472cb144c087393c1821911d09", - "reference": "56cd022adb5514472cb144c087393c1821911d09", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "predis/predis": "~1.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.13.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:06:54+00:00" - }, - { - "name": "doctrine/collections", - "version": "1.7.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "09dde3eb237756190f2de738d3c97cff10a8407b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/09dde3eb237756190f2de738d3c97cff10a8407b", - "reference": "09dde3eb237756190f2de738d3c97cff10a8407b", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "phpstan/phpstan": "^1.4.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.7.3" - }, - "time": "2022-09-01T19:34:23+00:00" - }, - { - "name": "doctrine/common", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "e09556bbdf95b8420e649162b19ae9da2d1a80f3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/e09556bbdf95b8420e649162b19ae9da2d1a80f3", - "reference": "e09556bbdf95b8420e649162b19ae9da2d1a80f3", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0 || ^3.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "doctrine/collections": "^1", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^4.0.5", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.4.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2022-08-23T19:46:56+00:00" - }, - { - "name": "doctrine/dbal", - "version": "2.13.8", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "dc9b3c3c8592c935a6e590441f9abc0f9eba335b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/dc9b3c3c8592c935a6e590441f9abc0f9eba335b", - "reference": "dc9b3c3c8592c935a6e590441f9abc0f9eba335b", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.0|^2.0", - "doctrine/deprecations": "^0.5.3", - "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.1 || ^8" - }, - "require-dev": { - "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.6", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.16", - "psalm/plugin-phpunit": "0.16.1", - "squizlabs/php_codesniffer": "3.6.2", - "symfony/cache": "^4.4", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.22.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlanywhere", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.8" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2022-03-09T15:25:46+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v0.5.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" - }, - "time": "2021-03-21T12:59:47+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", - "reference": "eb2ecf80e3093e8f3c2769ac838e27d8ede8e683", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "~1.4.10 || ^1.5.4", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2022-07-27T22:18:11+00:00" - }, - { - "name": "doctrine/inflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^8.2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "vimeo/psalm": "^4.10" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", - "keywords": [ - "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:16:43+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "doctrine/lexer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-02-28T11:07:21+00:00" - }, - { - "name": "doctrine/orm", - "version": "2.13.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/orm.git", - "reference": "35c44a56677adb3ce796138b6e4934ce93ec6811" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/35c44a56677adb3ce796138b6e4934ce93ec6811", - "reference": "35c44a56677adb3ce796138b6e4934ce93ec6811", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", - "doctrine/deprecations": "^0.5.3 || ^1", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.2.3", - "doctrine/persistence": "^2.4 || ^3", - "ext-ctype": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 2.0" - }, - "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/coding-standard": "^9.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.8.2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/log": "^1 || ^2 || ^3", - "squizlabs/php_codesniffer": "3.7.1", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.26.0" - }, - "suggest": { - "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", - "keywords": [ - "database", - "orm" - ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.13.1" - }, - "time": "2022-08-08T09:00:16+00:00" - }, - { - "name": "doctrine/persistence", - "version": "2.5.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/persistence.git", - "reference": "830c2ba42093e0e428eca37568ab36bd8008bc17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/830c2ba42093e0e428eca37568ab36bd8008bc17", - "reference": "830c2ba42093e0e428eca37568ab36bd8008bc17", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/collections": "^1.0", - "doctrine/deprecations": "^0.5.3 || ^1", - "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0" - }, - "conflict": { - "doctrine/annotations": "<1.0 || >=2.0", - "doctrine/common": "<2.10" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "~1.4.10 || 1.5.0", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.22.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src/Common", - "Doctrine\\Persistence\\": "src/Persistence" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", - "homepage": "https://doctrine-project.org/projects/persistence.html", - "keywords": [ - "mapper", - "object", - "odm", - "orm", - "persistence" - ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.5.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", - "type": "tidelift" - } - ], - "time": "2022-08-06T22:06:57+00:00" - }, - { - "name": "elasticsearch/elasticsearch", - "version": "v7.17.1", - "source": { - "type": "git", - "url": "git@github.com:elastic/elasticsearch-php.git", - "reference": "f1b8918f411b837ce5f6325e829a73518fd50367" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/f1b8918f411b837ce5f6325e829a73518fd50367", - "reference": "f1b8918f411b837ce5f6325e829a73518fd50367", - "shasum": "" - }, - "require": { - "ext-json": ">=1.3.7", - "ezimuel/ringphp": "^1.1.2", - "php": "^7.3 || ^8.0", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "ext-yaml": "*", - "ext-zip": "*", - "mockery/mockery": "^1.2", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.4", - "symfony/finder": "~4.0" - }, - "suggest": { - "ext-curl": "*", - "monolog/monolog": "Allows for client-level logging and tracing" - }, - "type": "library", - "autoload": { - "files": [ - "src/autoload.php" - ], - "psr-4": { - "Elasticsearch\\": "src/Elasticsearch/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0", - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Zachary Tong" - }, - { - "name": "Enrico Zimuel" - } - ], - "description": "PHP Client for Elasticsearch", - "keywords": [ - "client", - "elasticsearch", - "search" - ], - "time": "2022-09-30T12:28:55+00:00" - }, - { - "name": "ezimuel/guzzlestreams", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/ezimuel/guzzlestreams.git", - "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/b4b5a025dfee70d6cd34c780e07330eb93d5b997", - "reference": "b4b5a025dfee70d6cd34c780e07330eb93d5b997", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "stream" - ], - "support": { - "source": "https://github.com/ezimuel/guzzlestreams/tree/3.1.0" - }, - "time": "2022-10-24T12:58:50+00:00" - }, - { - "name": "ezimuel/ringphp", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/ezimuel/ringphp.git", - "reference": "7887fc8488013065f72f977dcb281994f5fde9f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/7887fc8488013065f72f977dcb281994f5fde9f4", - "reference": "7887fc8488013065f72f977dcb281994f5fde9f4", - "shasum": "" - }, - "require": { - "ezimuel/guzzlestreams": "^3.0.1", - "php": ">=5.4.0", - "react/promise": "~2.0" - }, - "replace": { - "guzzlehttp/ringphp": "self.version" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~9.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Ring\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", - "support": { - "source": "https://github.com/ezimuel/ringphp/tree/1.2.2" - }, - "time": "2022-12-07T11:28:53+00:00" - }, - { - "name": "fig/link-util", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/link-util.git", - "reference": "5d7b8d04ed3393b4b59968ca1e906fb7186d81e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/link-util/zipball/5d7b8d04ed3393b4b59968ca1e906fb7186d81e8", - "reference": "5d7b8d04ed3393b4b59968ca1e906fb7186d81e8", - "shasum": "" - }, - "require": { - "php": ">=5.5.0", - "psr/link": "~1.0@dev" - }, - "provide": { - "psr/link-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.1", - "squizlabs/php_codesniffer": "^2.3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Fig\\Link\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common utility implementations for HTTP links", - "keywords": [ - "http", - "http-link", - "link", - "psr", - "psr-13", - "rest" - ], - "support": { - "issues": "https://github.com/php-fig/link-util/issues", - "source": "https://github.com/php-fig/link-util/tree/1.1.2" - }, - "time": "2021-02-03T23:36:04+00:00" - }, - { - "name": "firebase/php-jwt", - "version": "v6.3.2", - "source": { - "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "ea7dda77098b96e666c5ef382452f94841e439cd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/ea7dda77098b96e666c5ef382452f94841e439cd", - "reference": "ea7dda77098b96e666c5ef382452f94841e439cd", - "shasum": "" - }, - "require": { - "php": "^7.1||^8.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^1.1", - "phpunit/phpunit": "^7.5||^9.5", - "psr/cache": "^1.0||^2.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" - }, - "type": "library", - "autoload": { - "psr-4": { - "Firebase\\JWT\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" - }, - { - "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" - } - ], - "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", - "keywords": [ - "jwt", - "php" - ], - "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.3.2" - }, - "time": "2022-12-19T17:10:46+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", - "shasum": "" - }, - "require": { - "composer/semver": "^3.2", - "composer/xdebug-handler": "^2.0", - "doctrine/annotations": "^1.12", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^7.2.5 || ^8.0", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^4.4.20 || ^5.1.3 || ^6.0", - "symfony/event-dispatcher": "^4.4.20 || ^5.0 || ^6.0", - "symfony/filesystem": "^4.4.20 || ^5.0 || ^6.0", - "symfony/finder": "^4.4.20 || ^5.0 || ^6.0", - "symfony/options-resolver": "^4.4.20 || ^5.0 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.23", - "symfony/polyfill-php81": "^1.23", - "symfony/process": "^4.4.20 || ^5.0 || ^6.0", - "symfony/stopwatch": "^4.4.20 || ^5.0 || ^6.0" - }, - "require-dev": { - "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.8", - "php-coveralls/php-coveralls": "^2.5.2", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^8.5.21 || ^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^5.2.4 || ^6.0", - "symfony/yaml": "^4.4.20 || ^5.0 || ^6.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2021-12-11T16:25:08+00:00" - }, - { - "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.12", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "8419f0158715b30d4b99a5bd37c6a39671994ad7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/8419f0158715b30d4b99a5bd37c6a39671994ad7", - "reference": "8419f0158715b30d4b99a5bd37c6a39671994ad7", - "shasum": "" - }, - "require": { - "laminas/laminas-code": "~3.4.1|^4.0", - "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0|^6.0" - }, - "conflict": { - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" - }, - "replace": { - "ocramius/proxy-manager": "^2.1" - }, - "require-dev": { - "ext-phar": "*", - "symfony/phpunit-bridge": "^5.4|^6.0" - }, - "type": "library", - "extra": { - "thanks": { - "name": "ocramius/proxy-manager", - "url": "https://github.com/Ocramius/ProxyManager" - } - }, - "autoload": { - "psr-4": { - "ProxyManager\\": "src/ProxyManager" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - } - ], - "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", - "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", - "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "support": { - "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.12" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", - "type": "tidelift" - } - ], - "time": "2022-05-05T09:31:05+00:00" - }, - { - "name": "google/auth", - "version": "v1.25.0", - "source": { - "type": "git", - "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "0865c44ab50378f7b145827dfcbd1e7a238f7759" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/0865c44ab50378f7b145827dfcbd1e7a238f7759", - "reference": "0865c44ab50378f7b145827dfcbd1e7a238f7759", - "shasum": "" - }, - "require": { - "firebase/php-jwt": "^5.5||^6.0", - "guzzlehttp/guzzle": "^6.2.1|^7.0", - "guzzlehttp/psr7": "^1.7|^2.0", - "php": "^7.1||^8.0", - "psr/cache": "^1.0|^2.0|^3.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "guzzlehttp/promises": "0.1.1|^1.3", - "kelvinmo/simplejwt": "^0.2.5|^0.5.1", - "phpseclib/phpseclib": "^2.0.31", - "phpspec/prophecy-phpunit": "^1.1||^2.0", - "phpunit/phpunit": "^7.5||^9.0.0", - "sebastian/comparator": ">=1.2.3", - "squizlabs/php_codesniffer": "^3.5" - }, - "suggest": { - "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." - }, - "type": "library", - "autoload": { - "psr-4": { - "Google\\Auth\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "Google Auth Library for PHP", - "homepage": "http://github.com/google/google-auth-library-php", - "keywords": [ - "Authentication", - "google", - "oauth2" - ], - "support": { - "docs": "https://googleapis.github.io/google-auth-library-php/main/", - "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.25.0" - }, - "time": "2023-01-26T22:04:14+00:00" - }, - { - "name": "google/cloud-core", - "version": "v1.49.0", - "source": { - "type": "git", - "url": "https://github.com/googleapis/google-cloud-php-core.git", - "reference": "5db0d164b61440312e99e4af9b43a59823098a8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-cloud-php-core/zipball/5db0d164b61440312e99e4af9b43a59823098a8d", - "reference": "5db0d164b61440312e99e4af9b43a59823098a8d", - "shasum": "" - }, - "require": { - "google/auth": "^1.18", - "guzzlehttp/guzzle": "^5.3|^6.5.7|^7.4.4", - "guzzlehttp/promises": "^1.3", - "guzzlehttp/psr7": "^1.7|^2.0", - "monolog/monolog": "^1.1|^2.0|^3.0", - "php": ">=5.6", - "psr/http-message": "1.0.*", - "rize/uri-template": "~0.3" - }, - "require-dev": { - "erusev/parsedown": "^1.6", - "google/cloud-common-protos": "^0.3", - "google/gax": "^1.9", - "opis/closure": "^3", - "phpdocumentor/reflection": "^3.0||^4.0||^5.3", - "phpspec/prophecy": "^1.10.3", - "phpunit/phpunit": "^4.8|^5.0|^8.0", - "squizlabs/php_codesniffer": "2.*", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "opis/closure": "May be used to serialize closures to process jobs in the batch daemon. Please require version ^3.", - "symfony/lock": "Required for the Spanner cached based session pool. Please require the following commit: 3.3.x-dev#1ba6ac9" - }, - "bin": [ - "bin/google-cloud-batch" - ], - "type": "library", - "extra": { - "component": { - "id": "cloud-core", - "target": "googleapis/google-cloud-php-core.git", - "path": "Core", - "entry": "src/ServiceBuilder.php" - } - }, - "autoload": { - "psr-4": { - "Google\\Cloud\\Core\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "Google Cloud PHP shared dependency, providing functionality useful to all components.", - "support": { - "source": "https://github.com/googleapis/google-cloud-php-core/tree/v1.49.0" - }, - "time": "2023-01-27T18:26:22+00:00" - }, - { - "name": "google/cloud-storage", - "version": "v1.28.1", - "source": { - "type": "git", - "url": "https://github.com/googleapis/google-cloud-php-storage.git", - "reference": "83e8beac404f38d2e869da4c3fbb7bdf96193f77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-cloud-php-storage/zipball/83e8beac404f38d2e869da4c3fbb7bdf96193f77", - "reference": "83e8beac404f38d2e869da4c3fbb7bdf96193f77", - "shasum": "" - }, - "require": { - "google/cloud-core": "^1.43", - "google/crc32": "^0.1.0" - }, - "require-dev": { - "erusev/parsedown": "^1.6", - "google/cloud-pubsub": "^1.0", - "phpdocumentor/reflection": "^3.0||^4.0", - "phpseclib/phpseclib": "^2.0||^3.0", - "phpspec/prophecy": "^1.10.3", - "phpunit/phpunit": "^4.8|^5.0|^8.0", - "squizlabs/php_codesniffer": "2.*", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "google/cloud-pubsub": "May be used to register a topic to receive bucket notifications.", - "phpseclib/phpseclib": "May be used in place of OpenSSL for creating signed Cloud Storage URLs. Please require version ^2." - }, - "type": "library", - "extra": { - "component": { - "id": "cloud-storage", - "target": "googleapis/google-cloud-php-storage.git", - "path": "Storage", - "entry": "src/StorageClient.php" - } - }, - "autoload": { - "psr-4": { - "Google\\Cloud\\Storage\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "Cloud Storage Client for PHP", - "support": { - "source": "https://github.com/googleapis/google-cloud-php-storage/tree/v1.28.1" - }, - "time": "2022-08-23T20:22:22+00:00" - }, - { - "name": "google/crc32", - "version": "v0.1.0", - "source": { - "type": "git", - "url": "https://github.com/google/php-crc32.git", - "reference": "a8525f0dea6fca1893e1bae2f6e804c5f7d007fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/google/php-crc32/zipball/a8525f0dea6fca1893e1bae2f6e804c5f7d007fb", - "reference": "a8525f0dea6fca1893e1bae2f6e804c5f7d007fb", - "shasum": "" - }, - "require": { - "php": ">=5.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^1.13 || v2.14.2", - "paragonie/random_compat": ">=2", - "phpunit/phpunit": "^4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Google\\CRC32\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andrew Brampton", - "email": "bramp@google.com" - } - ], - "description": "Various CRC32 implementations", - "homepage": "https://github.com/google/php-crc32", - "support": { - "issues": "https://github.com/google/php-crc32/issues", - "source": "https://github.com/google/php-crc32/tree/v0.1.0" - }, - "time": "2019-05-09T06:24:58+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "7.5.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2022-08-28T15:39:27+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "1.5.2", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2022-08-28T14:55:35+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2022-08-28T14:45:39+00:00" - }, - { - "name": "laminas/laminas-code", - "version": "4.6.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "16ec7577ff315d53ac2e1b1f03a344d8fe680a6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/16ec7577ff315d53ac2e1b1f03a344d8fe680a6e", - "reference": "16ec7577ff315d53ac2e1b1f03a344d8fe680a6e", - "shasum": "" - }, - "require": { - "php": ">=7.4, <8.2" - }, - "require-dev": { - "doctrine/annotations": "^1.13.2", - "ext-phar": "*", - "laminas/laminas-coding-standard": "^2.3.0", - "laminas/laminas-stdlib": "^3.6.1", - "phpunit/phpunit": "^9.5.10", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.13.1" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" - }, - "type": "library", - "autoload": { - "files": [ - "polyfill/ReflectionEnumPolyfill.php" - ], - "psr-4": { - "Laminas\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", - "keywords": [ - "code", - "laminas", - "laminasframework" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-07-28T22:46:52+00:00" - }, - { - "name": "laminas/laminas-escaper", - "version": "2.10.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-escaper.git", - "reference": "58af67282db37d24e584a837a94ee55b9c7552be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be", - "reference": "58af67282db37d24e584a837a94ee55b9c7552be", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-mbstring": "*", - "php": "^7.4 || ~8.0.0 || ~8.1.0" - }, - "conflict": { - "zendframework/zend-escaper": "*" - }, - "require-dev": { - "infection/infection": "^0.26.6", - "laminas/laminas-coding-standard": "~2.3.0", - "maglnet/composer-require-checker": "^3.8.0", - "phpunit/phpunit": "^9.5.18", - "psalm/plugin-phpunit": "^0.16.1", - "vimeo/psalm": "^4.22.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Escaper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", - "homepage": "https://laminas.dev", - "keywords": [ - "escaper", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-escaper/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-escaper/issues", - "rss": "https://github.com/laminas/laminas-escaper/releases.atom", - "source": "https://github.com/laminas/laminas-escaper" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-03-08T20:15:36+00:00" - }, - { - "name": "league/flysystem", - "version": "1.1.10", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", - "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "league/mime-type-detection": "^1.3", - "php": "^7.2.5 || ^8.0" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "phpspec/prophecy": "^1.11.1", - "phpunit/phpunit": "^8.5.8" - }, - "suggest": { - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Filesystem abstraction: Many filesystems, one API.", - "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "support": { - "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" - }, - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } - ], - "time": "2022-10-04T09:16:37+00:00" - }, - { - "name": "league/flysystem-aws-s3-v3", - "version": "1.0.30", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "af286f291ebab6877bac0c359c6c2cb017eb061d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/af286f291ebab6877bac0c359c6c2cb017eb061d", - "reference": "af286f291ebab6877bac0c359c6c2cb017eb061d", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "^3.20.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "support": { - "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.30" - }, - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2022-07-02T13:51:38+00:00" - }, - { - "name": "league/mime-type-detection", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\MimeTypeDetection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "Mime-type detection for Flysystem", - "support": { - "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" - }, - "funding": [ - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2022-04-17T13:12:02+00:00" - }, - { - "name": "monolog/monolog", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7 || ^8", - "ext-json": "*", - "graylog2/gelf-php": "^1.4.2", - "guzzlehttp/guzzle": "^7.4", - "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", - "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", - "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", - "symfony/mailer": "^5.4 || ^6", - "symfony/mime": "^5.4 || ^6" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", - "ext-mbstring": "Allow to work properly with unicode symbols", - "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", - "ext-openssl": "Required to send log messages using SSL", - "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "https://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "support": { - "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.8.0" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], - "time": "2022-07-24T11:55:47+00:00" - }, - { - "name": "mpdf/mpdf", - "version": "v8.1.1", - "source": { - "type": "git", - "url": "https://github.com/mpdf/mpdf.git", - "reference": "e511e89a66bdb066e3fbf352f00f4734d5064cbf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mpdf/mpdf/zipball/e511e89a66bdb066e3fbf352f00f4734d5064cbf", - "reference": "e511e89a66bdb066e3fbf352f00f4734d5064cbf", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "ext-mbstring": "*", - "myclabs/deep-copy": "^1.7", - "paragonie/random_compat": "^1.4|^2.0|^9.99.99", - "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0", - "php-http/message-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/log": "^1.0 || ^2.0", - "setasign/fpdi": "^2.1" - }, - "require-dev": { - "mockery/mockery": "^1.3.0", - "mpdf/qrcode": "^1.1.0", - "squizlabs/php_codesniffer": "^3.5.0", - "tracy/tracy": "^2.4", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "ext-bcmath": "Needed for generation of some types of barcodes", - "ext-xml": "Needed mainly for SVG manipulation", - "ext-zlib": "Needed for compression of embedded resources, such as fonts" - }, - "type": "library", - "autoload": { - "psr-4": { - "Mpdf\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-only" - ], - "authors": [ - { - "name": "Matěj Humpál", - "role": "Developer, maintainer" - }, - { - "name": "Ian Back", - "role": "Developer (retired)" - } - ], - "description": "PHP library generating PDF files from UTF-8 encoded HTML", - "homepage": "https://mpdf.github.io", - "keywords": [ - "pdf", - "php", - "utf-8" - ], - "support": { - "docs": "http://mpdf.github.io", - "issues": "https://github.com/mpdf/mpdf/issues", - "source": "https://github.com/mpdf/mpdf" - }, - "funding": [ - { - "url": "https://www.paypal.me/mpdf", - "type": "custom" - } - ], - "time": "2022-04-18T11:50:28+00:00" - }, - { - "name": "mtdowling/jmespath.php", - "version": "2.6.1", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "shasum": "" - }, - "require": { - "php": "^5.4 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.17" - }, - "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" - }, - "bin": [ - "bin/jp.php" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "files": [ - "src/JmesPath.php" - ], - "psr-4": { - "JmesPath\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Declaratively specify how to extract elements from a JSON document", - "keywords": [ - "json", - "jsonpath" - ], - "support": { - "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" - }, - "time": "2021-06-14T00:11:39+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2022-03-03T13:19:32+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.13.2", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" - }, - "time": "2021-11-30T19:35:32+00:00" - }, - { - "name": "ongr/elasticsearch-dsl", - "version": "v7.2.2", - "source": { - "type": "git", - "url": "https://github.com/ongr-io/ElasticsearchDSL.git", - "reference": "c0789c35e8738c2b1138c8d33ec9fbcd740c909d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ongr-io/ElasticsearchDSL/zipball/c0789c35e8738c2b1138c8d33ec9fbcd740c909d", - "reference": "c0789c35e8738c2b1138c8d33ec9fbcd740c909d", - "shasum": "" - }, - "require": { - "elasticsearch/elasticsearch": "^7.0", - "php": "^7.4 || ^8.0", - "symfony/serializer": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.2-dev" - } - }, - "autoload": { - "psr-4": { - "ONGR\\ElasticsearchDSL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "ONGR team", - "homepage": "http://www.ongr.io" - } - ], - "description": "Elasticsearch DSL library", - "homepage": "http://ongr.io", - "support": { - "issues": "https://github.com/ongr-io/ElasticsearchDSL/issues", - "source": "https://github.com/ongr-io/ElasticsearchDSL/tree/v7.2.2" - }, - "time": "2021-04-27T10:58:40+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" - }, - "time": "2021-02-23T14:00:09+00:00" - }, - { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" - }, - "time": "2020-10-14T08:32:19+00:00" - }, - { - "name": "php-http/message-factory", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-http/message-factory.git", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Factory interfaces for PSR-7 HTTP Message", - "homepage": "http://php-http.org", - "keywords": [ - "factory", - "http", - "message", - "stream", - "uri" - ], - "support": { - "issues": "https://github.com/php-http/message-factory/issues", - "source": "https://github.com/php-http/message-factory/tree/master" - }, - "time": "2015-12-19T14:08:53+00:00" - }, - { - "name": "php-parallel-lint/php-parallel-lint", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git", - "reference": "761f3806e30239b5fcd90a0a45d41dc2138de192" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/761f3806e30239b5fcd90a0a45d41dc2138de192", - "reference": "761f3806e30239b5fcd90a0a45d41dc2138de192", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=5.3.0" - }, - "replace": { - "grogy/php-parallel-lint": "*", - "jakub-onderka/php-parallel-lint": "*" - }, - "require-dev": { - "nette/tester": "^1.3 || ^2.0", - "php-parallel-lint/php-console-highlighter": "~0.3", - "squizlabs/php_codesniffer": "^3.6" - }, - "suggest": { - "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" - }, - "bin": [ - "parallel-lint" - ], - "type": "library", - "autoload": { - "classmap": [ - "./" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "ahoj@jakubonderka.cz" - } - ], - "description": "This tool check syntax of PHP files about 20x faster than serial check.", - "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", - "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", - "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.1" - }, - "time": "2021-08-13T05:35:13+00:00" - }, - { - "name": "phpcompatibility/php-compatibility", - "version": "9.3.5", - "source": { - "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", - "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" - }, - "conflict": { - "squizlabs/php_codesniffer": "2.6.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" - }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." - }, - "type": "phpcodesniffer-standard", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Wim Godden", - "homepage": "https://github.com/wimg", - "role": "lead" - }, - { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl", - "role": "lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" - } - ], - "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", - "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", - "keywords": [ - "compatibility", - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", - "source": "https://github.com/PHPCompatibility/PHPCompatibility" - }, - "time": "2019-12-27T09:44:58+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" - }, - "time": "2022-10-14T12:47:21+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, - { - "name": "phpspec/prophecy-phpunit", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy-phpunit.git", - "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/2d7a9df55f257d2cba9b1d0c0963a54960657177", - "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8", - "phpspec/prophecy": "^1.3", - "phpunit/phpunit": "^9.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\PhpUnit\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christophe Coevoet", - "email": "stof@notk.org" - } - ], - "description": "Integrating the Prophecy mocking library in PHPUnit test cases", - "homepage": "http://phpspec.net", - "keywords": [ - "phpunit", - "prophecy" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy-phpunit/issues", - "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.0.1" - }, - "time": "2020-07-09T08:33:42+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-05T09:12:13+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.5.11", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2406855036db1102126125537adb1406f7242fdd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2406855036db1102126125537adb1406f7242fdd", - "reference": "2406855036db1102126125537adb1406f7242fdd", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", - "sebastian/version": "^3.0.2" - }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ], - "files": [ - "src/Framework/Assert/Functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.11" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-25T07:07:57+00:00" - }, - { - "name": "psalm/phar", - "version": "4.17.0", - "source": { - "type": "git", - "url": "https://github.com/psalm/phar.git", - "reference": "8e968d791ffbb253901afc4b3512445d96cb1c51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/psalm/phar/zipball/8e968d791ffbb253901afc4b3512445d96cb1c51", - "reference": "8e968d791ffbb253901afc4b3512445d96cb1c51", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "vimeo/psalm": "*" - }, - "bin": [ - "psalm.phar" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Composer-based Psalm Phar", - "support": { - "issues": "https://github.com/psalm/phar/issues", - "source": "https://github.com/psalm/phar/tree/4.17.0" - }, - "time": "2022-01-01T19:35:43+00:00" - }, - { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/master" - }, - "time": "2016-08-06T20:24:11+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client/tree/master" - }, - "time": "2020-06-29T06:28:15+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" - }, - "time": "2019-04-30T12:38:16+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "psr/link", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/link.git", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Link\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for HTTP links", - "keywords": [ - "http", - "http-link", - "link", - "psr", - "psr-13", - "rest" - ], - "support": { - "source": "https://github.com/php-fig/link/tree/master" - }, - "time": "2016-10-28T16:06:13+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "ramsey/collection", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "symfony/polyfill-php81": "^1.23" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-27T19:12:24+00:00" - }, - { - "name": "ramsey/uuid", - "version": "4.2.3", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "shasum": "" - }, - "require": { - "brick/math": "^0.8 || ^0.9", - "ext-json": "*", - "php": "^7.2 || ^8.0", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php80": "^1.14" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.10", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5 || ^9", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.x-dev" - }, - "captainhook": { - "force-install": true - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.2.3" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2021-09-25T23:10:38+00:00" - }, - { - "name": "react/promise", - "version": "v2.9.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.9.0" - }, - "funding": [ - { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-02-11T10:27:51+00:00" - }, - { - "name": "rize/uri-template", - "version": "0.3.5", - "source": { - "type": "git", - "url": "https://github.com/rize/UriTemplate.git", - "reference": "5ed4ba8ea34af84485dea815d4b6b620794d1168" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rize/UriTemplate/zipball/5ed4ba8ea34af84485dea815d4b6b620794d1168", - "reference": "5ed4ba8ea34af84485dea815d4b6b620794d1168", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "Rize\\": "src/Rize" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marut K", - "homepage": "http://twitter.com/rezigned" - } - ], - "description": "PHP URI Template (RFC 6570) supports both expansion & extraction", - "keywords": [ - "RFC 6570", - "template", - "uri" - ], - "support": { - "issues": "https://github.com/rize/UriTemplate/issues", - "source": "https://github.com/rize/UriTemplate/tree/0.3.5" - }, - "funding": [ - { - "url": "https://www.paypal.me/rezigned", - "type": "custom" - }, - { - "url": "https://github.com/rezigned", - "type": "github" - }, - { - "url": "https://opencollective.com/rize-uri-template", - "type": "open_collective" - } - ], - "time": "2022-10-12T17:22:51+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:49:45+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:52:27+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:10:38+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:52:38+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-11-11T14:18:36+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-06-11T13:31:12+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-28T06:42:11+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:17:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:45:17+00:00" - }, - { - "name": "sebastian/type", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-06-15T12:49:02+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "setasign/fpdf", - "version": "1.8.4", - "source": { - "type": "git", - "url": "https://github.com/Setasign/FPDF.git", - "reference": "b0ddd9c5b98ced8230ef38534f6f3c17308a7974" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDF/zipball/b0ddd9c5b98ced8230ef38534f6f3c17308a7974", - "reference": "b0ddd9c5b98ced8230ef38534f6f3c17308a7974", - "shasum": "" - }, - "require": { - "ext-gd": "*", - "ext-zlib": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "fpdf.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Olivier Plathey", - "email": "oliver@fpdf.org", - "homepage": "http://fpdf.org/" - } - ], - "description": "FPDF is a PHP class which allows to generate PDF files with pure PHP. F from FPDF stands for Free: you may use it for any kind of usage and modify it to suit your needs.", - "homepage": "http://www.fpdf.org", - "keywords": [ - "fpdf", - "pdf" - ], - "support": { - "source": "https://github.com/Setasign/FPDF/tree/1.8.4" - }, - "time": "2021-08-30T07:50:06+00:00" - }, - { - "name": "setasign/fpdi", - "version": "v2.3.6", - "source": { - "type": "git", - "url": "https://github.com/Setasign/FPDI.git", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31", - "shasum": "" - }, - "require": { - "ext-zlib": "*", - "php": "^5.6 || ^7.0 || ^8.0" - }, - "conflict": { - "setasign/tfpdf": "<1.31" - }, - "require-dev": { - "phpunit/phpunit": "~5.7", - "setasign/fpdf": "~1.8", - "setasign/tfpdf": "1.31", - "squizlabs/php_codesniffer": "^3.5", - "tecnickcom/tcpdf": "~6.2" - }, - "suggest": { - "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." - }, - "type": "library", - "autoload": { - "psr-4": { - "setasign\\Fpdi\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Slabon", - "email": "jan.slabon@setasign.com", - "homepage": "https://www.setasign.com" - }, - { - "name": "Maximilian Kresse", - "email": "maximilian.kresse@setasign.com", - "homepage": "https://www.setasign.com" - } - ], - "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", - "homepage": "https://www.setasign.com/fpdi", - "keywords": [ - "fpdf", - "fpdi", - "pdf" - ], - "support": { - "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.3.6" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", - "type": "tidelift" - } - ], - "time": "2021-02-11T11:37:01+00:00" - }, - { - "name": "shopware/shopware", - "version": "v5.7.16", - "source": { - "type": "git", - "url": "https://github.com/shopware/shopware.git", - "reference": "a7de7fc4d3ffb3f1da82a17ac5f63aa398195dee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/shopware/shopware/zipball/a7de7fc4d3ffb3f1da82a17ac5f63aa398195dee", - "reference": "a7de7fc4d3ffb3f1da82a17ac5f63aa398195dee", - "shasum": "" - }, - "require": { - "bcremer/line-reader": "1.2.0", - "beberlei/assert": "3.3.2", - "beberlei/doctrineextensions": "1.3.0", - "cocur/slugify": "4.2.0", - "composer-runtime-api": "^2.0", - "doctrine/annotations": "1.13.3", - "doctrine/cache": "1.13.0", - "doctrine/collections": "1.7.3", - "doctrine/common": "3.4.0", - "doctrine/dbal": "2.13.8", - "doctrine/event-manager": "1.1.2", - "doctrine/inflector": "2.0.4", - "doctrine/orm": "2.13.1", - "doctrine/persistence": "2.5.4", - "elasticsearch/elasticsearch": "^7", - "ext-ctype": "*", - "ext-curl": "*", - "ext-date": "*", - "ext-dom": "*", - "ext-filter": "*", - "ext-gd": "*", - "ext-hash": "*", - "ext-iconv": "*", - "ext-intl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-openssl": "*", - "ext-pdo": "*", - "ext-pdo_mysql": "*", - "ext-session": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-zip": "*", - "ext-zlib": "*", - "fig/link-util": "1.1.2", - "friendsofphp/proxy-manager-lts": "1.0.12", - "google/cloud-storage": "1.28.1", - "guzzlehttp/guzzle": "~7.5.0", - "guzzlehttp/psr7": "2.4.1", - "laminas/laminas-code": "4.6.0", - "laminas/laminas-escaper": "2.10.0", - "league/flysystem": "~1.1.4", - "league/flysystem-aws-s3-v3": "1.0.30", - "lib-libxml": "*", - "monolog/monolog": "2.8.0", - "mpdf/mpdf": "8.1.1", - "ongr/elasticsearch-dsl": "7.2.2", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0", - "psr/link": "1.0.0", - "psr/log": "1.1.4", - "ramsey/uuid": "4.2.3", - "setasign/fpdf": "1.8.4", - "setasign/fpdi": "2.3.6", - "stecman/symfony-console-completion": "0.11.0", - "superbalist/flysystem-google-storage": "7.2.2", - "symfony/config": "~4.4.34", - "symfony/console": "~4.4.34", - "symfony/dependency-injection": "~4.4.34", - "symfony/expression-language": "~4.4.34", - "symfony/filesystem": "~4.4.27", - "symfony/finder": "~4.4.30", - "symfony/form": "~4.4.34", - "symfony/http-foundation": "~4.4.34", - "symfony/http-kernel": "~4.4.34", - "symfony/options-resolver": "~4.4.30", - "symfony/polyfill-php80": "^1.23", - "symfony/polyfill-php81": "^1.23", - "symfony/process": "~4.4.34", - "symfony/serializer": "~5.4.0", - "symfony/validator": "~4.4.34", - "symfony/web-link": "~4.4.27", - "voku/anti-xss": "~4.1.39", - "wikimedia/less.php": "3.1.0" - }, - "replace": { - "paragonie/random_compat": "*", - "symfony/polyfill-ctype": "*", - "symfony/polyfill-iconv": "*", - "symfony/polyfill-mbstring": "*", - "symfony/polyfill-php72": "*", - "symfony/polyfill-php73": "*" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "1.8.1", - "behat/behat": "3.11.0", - "behat/gherkin": "4.9.0", - "behat/mink": "1.10.0", - "behat/mink-selenium2-driver": "1.6.0", - "friends-of-behat/mink-extension": "2.7.1", - "php-parallel-lint/php-var-dump-check": "^0.5", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "1.1.0", - "phpstan/phpstan": "1.8.5", - "phpstan/phpstan-doctrine": "1.3.13", - "phpstan/phpstan-phpunit": "1.1.1", - "phpstan/phpstan-symfony": "1.2.13", - "phpunit/phpunit": "^9.4", - "sensiolabs/behat-page-object-extension": "2.3.5", - "staabm/phpstan-dba": "0.2.42", - "symfony/browser-kit": "~4.4.27", - "symfony/dom-crawler": "~4.4.30" - }, - "suggest": { - "ext-apcu": "*", - "ext-zend-opcache": "*" - }, - "type": "project", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "files": [ - "engine/Shopware/Shopware.php" - ], - "psr-0": { - "Zend": "engine/Library/", - "JSMin": "engine/Library/minify/", - "Enlight": "engine/Library/", - "Shopware": "engine/", - "Doctrine\\Common\\Proxy\\AbstractProxyFactory": "engine/Library/", - "Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister": "engine/Library/" - }, - "classmap": [ - "engine/Shopware/", - "engine/Library/Smarty/" - ], - "exclude-from-classmap": [ - "engine/Shopware/Plugins/Community/", - "engine/Shopware/Plugins/Local/", - "custom/plugins/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "engine/Library/" - ], - "license": [ - "AGPL-3.0", - "proprietary" - ], - "description": "Shopware is the next generation of open source e-commerce software made in Germany", - "homepage": "http://www.shopware.com", - "keywords": [ - "shop", - "shopware" - ], - "support": { - "chat": "https://slack.shopware.com", - "forum": "https://forum.shopware.com", - "issues": "https://issues.shopware.com", - "source": "https://github.com/shopware/shopware", - "wiki": "https://developers.shopware.com/" - }, - "time": "2022-11-02T09:29:39+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.6.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2021-12-12T21:44:58+00:00" - }, - { - "name": "stecman/symfony-console-completion", - "version": "0.11.0", - "source": { - "type": "git", - "url": "https://github.com/stecman/symfony-console-completion.git", - "reference": "a9502dab59405e275a9f264536c4e1cb61fc3518" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/stecman/symfony-console-completion/zipball/a9502dab59405e275a9f264536c4e1cb61fc3518", - "reference": "a9502dab59405e275a9f264536c4e1cb61fc3518", - "shasum": "" - }, - "require": { - "php": ">=5.3.2", - "symfony/console": "~2.3 || ~3.0 || ~4.0 || ~5.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8.36 || ~5.7 || ~6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.10.x-dev" - } - }, - "autoload": { - "psr-4": { - "Stecman\\Component\\Symfony\\Console\\BashCompletion\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stephen Holdaway", - "email": "stephen@stecman.co.nz" - } - ], - "description": "Automatic BASH completion for Symfony Console Component based applications.", - "support": { - "issues": "https://github.com/stecman/symfony-console-completion/issues", - "source": "https://github.com/stecman/symfony-console-completion/tree/0.11.0" - }, - "time": "2019-11-24T17:03:06+00:00" - }, - { - "name": "superbalist/flysystem-google-storage", - "version": "7.2.2", - "source": { - "type": "git", - "url": "https://github.com/Superbalist/flysystem-google-cloud-storage.git", - "reference": "87e2f450c0e4b5200fef9ffe6863068cc873d734" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Superbalist/flysystem-google-cloud-storage/zipball/87e2f450c0e4b5200fef9ffe6863068cc873d734", - "reference": "87e2f450c0e4b5200fef9ffe6863068cc873d734", - "shasum": "" - }, - "require": { - "google/cloud-storage": "~1.0", - "league/flysystem": "~1.0", - "php": ">=5.5.0" - }, - "require-dev": { - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Superbalist\\Flysystem\\GoogleStorage\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Superbalist.com a division of Takealot Online (Pty) Ltd", - "email": "info@superbalist.com" - } - ], - "description": "Flysystem adapter for Google Cloud Storage", - "support": { - "issues": "https://github.com/Superbalist/flysystem-google-cloud-storage/issues", - "source": "https://github.com/Superbalist/flysystem-google-cloud-storage/tree/7.2.2" - }, - "time": "2019-10-10T12:22:54+00:00" - }, - { - "name": "symfony/cache", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "e9147c89fdfdc5d5ef798bb7193f23726ad609f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/e9147c89fdfdc5d5ef798bb7193f23726ad609f5", - "reference": "e9147c89fdfdc5d5ef798bb7193f23726ad609f5", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" - }, - "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-19T09:49:58+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/config", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", - "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/filesystem": "^3.4|^4.0|^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" - }, - "conflict": { - "symfony/finder": "<3.4" - }, - "require-dev": { - "symfony/event-dispatcher": "^3.4|^4.0|^5.0", - "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/messenger": "^4.1|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/console", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", - "symfony/lock": "<4.4", - "symfony/process": "<3.3" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/console/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-05T17:10:16+00:00" - }, - { - "name": "symfony/debug", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "abandoned": "symfony/error-handler", - "time": "2022-07-28T16:29:46+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9065fe97dbd38a897e95ea254eb5ddfe1310f734", - "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/container": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1.6|^2" - }, - "conflict": { - "symfony/config": "<4.3|>=5.0", - "symfony/finder": "<3.4", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<4.4.26" - }, - "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" - }, - "require-dev": { - "symfony/config": "^4.3", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/yaml": "^4.4.26|^5.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-16T16:18:09+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/error-handler", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "be731658121ef2d8be88f3a1ec938148a9237291" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/be731658121ef2d8be88f3a1ec938148a9237291", - "reference": "be731658121ef2d8be88f3a1ec938148a9237291", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3", - "symfony/debug": "^4.4.5", - "symfony/var-dumper": "^4.4|^5.0" - }, - "require-dev": { - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to manage errors and ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/error-handler/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-28T16:29:46+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "~3.4|~4.4", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e", - "shasum": "" - }, - "require": { - "php": ">=7.1.3" - }, - "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:41:36+00:00" - }, - { - "name": "symfony/expression-language", - "version": "v4.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/expression-language.git", - "reference": "e4964c7636e19f6008660f450c09121c80c2a7b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/e4964c7636e19f6008660f450c09121c80c2a7b9", - "reference": "e4964c7636e19f6008660f450c09121c80c2a7b9", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/cache": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ExpressionLanguage\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an engine that can compile and evaluate expressions", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/expression-language/tree/v4.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-03T15:15:11+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v4.4.42", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v4.4.42" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-20T08:49:14+00:00" - }, - { - "name": "symfony/finder", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-29T07:35:46+00:00" - }, - { - "name": "symfony/form", - "version": "v4.4.48", - "source": { - "type": "git", - "url": "https://github.com/symfony/form.git", - "reference": "e1d137b13e0ec2cb5c5e38debca7a510c6f858c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/e1d137b13e0ec2cb5c5e38debca7a510c6f858c6", - "reference": "e1d137b13e0ec2cb5c5e38debca7a510c6f858c6", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher": "^4.3", - "symfony/intl": "^4.4|^5.0", - "symfony/options-resolver": "~4.3|^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/property-access": "^3.4.40|^4.4.8|^5.0.8", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/console": "<4.3", - "symfony/dependency-injection": "<3.4", - "symfony/doctrine-bridge": "<3.4", - "symfony/framework-bundle": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.3", - "symfony/translation": "<4.2", - "symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0" - }, - "require-dev": { - "doctrine/collections": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^4.3|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/security-csrf": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", - "symfony/validator": "^4.4.17|^5.1.9", - "symfony/var-dumper": "^4.3|^5.0" - }, - "suggest": { - "symfony/security-csrf": "For protecting forms against CSRF attacks.", - "symfony/twig-bridge": "For templating with Twig.", - "symfony/validator": "For form validation." - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Form\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows to easily create, process and reuse HTML forms", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/form/tree/v4.4.48" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-22T05:50:33+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/http-client-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-12T15:48:08+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "191413c7b832c015bb38eae963f2e57498c3c173" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/191413c7b832c015bb38eae963f2e57498c3c173", - "reference": "191413c7b832c015bb38eae963f2e57498c3c173", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/mime": "^4.3|^5.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Defines an object-oriented layer for the HTTP specification", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-foundation/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-04T16:17:57+00:00" - }, - { - "name": "symfony/http-kernel", - "version": "v4.4.50", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "aa6df6c045f034aa13ac752fc234bb300b9488ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aa6df6c045f034aa13ac752fc234bb300b9488ef", - "reference": "aa6df6c045f034aa13ac752fc234bb300b9488ef", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2", - "symfony/error-handler": "^4.4", - "symfony/event-dispatcher": "^4.4", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4.30|^5.3.7", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/browser-kit": "<4.3", - "symfony/config": "<3.4", - "symfony/console": ">=5", - "symfony/dependency-injection": "<4.3", - "symfony/translation": "<4.2", - "twig/twig": "<1.43|<2.13,>=2" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.3|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0", - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^4.3|^5.0", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/routing": "^3.4|^4.0|^5.0", - "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/templating": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^1.43|^2.13|^3.0.4" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a structured process for converting a Request into a Response", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-kernel/tree/v4.4.50" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-02-01T08:01:31+00:00" - }, - { - "name": "symfony/intl", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/intl.git", - "reference": "f378eb62448dfea67071f9f43529d3a6ad7e0bc8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/f378eb62448dfea67071f9f43529d3a6ad7e0bc8", - "reference": "f378eb62448dfea67071f9f43529d3a6ad7e0bc8", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/filesystem": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\Intl\\": "" - }, - "classmap": [ - "Resources/stubs" - ], - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - }, - { - "name": "Eriksen Costa", - "email": "eriksen.costa@infranology.com.br" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", - "homepage": "https://symfony.com", - "keywords": [ - "i18n", - "icu", - "internationalization", - "intl", - "l10n", - "localization" - ], - "support": { - "source": "https://github.com/symfony/intl/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-11T13:51:47+00:00" - }, - { - "name": "symfony/mime", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "a858429a9c704edc53fe057228cf9ca282ba48eb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/a858429a9c704edc53fe057228cf9ca282ba48eb", - "reference": "a858429a9c704edc53fe057228cf9ca282ba48eb", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4", - "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.1|^6.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows manipulating MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-09T05:43:46+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "583f56160f716dd435f1cd721fd14b548f4bb510" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/583f56160f716dd435f1cd721fd14b548f4bb510", - "reference": "583f56160f716dd435f1cd721fd14b548f4bb510", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/process", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T13:16:42+00:00" - }, - { - "name": "symfony/property-access", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "20fcf370aed6b2b4a2d8170fa23d2d07250e94ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/20fcf370aed6b2b4a2d8170fa23d2d07250e94ab", - "reference": "20fcf370aed6b2b4a2d8170fa23d2d07250e94ab", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/property-info": "^5.2|^6.0" - }, - "require-dev": { - "symfony/cache": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/cache-implementation": "To cache access methods." - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides functions to read and write from/to an object or array using a simple string notation", - "homepage": "https://symfony.com", - "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", - "property", - "property path", - "reflection" - ], - "support": { - "source": "https://github.com/symfony/property-access/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-01T08:32:19+00:00" - }, - { - "name": "symfony/property-info", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "8ccf54bce2e2edbface1e99cb5a2560a290c9e2d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/8ccf54bce2e2edbface1e99cb5a2560a290c9e2d", - "reference": "8ccf54bce2e2edbface1e99cb5a2560a290c9e2d", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", - "homepage": "https://symfony.com", - "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" - ], - "support": { - "source": "https://github.com/symfony/property-info/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-14T11:26:56+00:00" - }, - { - "name": "symfony/serializer", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/serializer.git", - "reference": "2139fa01c19a764af81191d635b2b9302f4bafd8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/2139fa01c19a764af81191d635b2b9302f4bafd8", - "reference": "2139fa01c19a764af81191d635b2b9302f4bafd8", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.12", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0|>=1.7.0", - "symfony/dependency-injection": "<4.4", - "symfony/property-access": "<5.4", - "symfony/property-info": "<5.3.13", - "symfony/uid": "<5.3", - "symfony/yaml": "<4.4" - }, - "require-dev": { - "doctrine/annotations": "^1.12|^2", - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.3.13|^6.0", - "symfony/uid": "^5.3|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0", - "symfony/var-exporter": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/cache-implementation": "For using the metadata cache.", - "symfony/config": "For using the XML mapping loader.", - "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/property-info": "To deserialize relations.", - "symfony/var-exporter": "For using the metadata compiler.", - "symfony/yaml": "For using the default YAML mapping loader." - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Serializer\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/serializer/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-14T08:18:46+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-30T19:17:29+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v5.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "208ef96122bfed82a8f3a61458a07113a08bdcfe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/208ef96122bfed82a8f3a61458a07113a08bdcfe", - "reference": "208ef96122bfed82a8f3a61458a07113a08bdcfe", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-23T10:19:22+00:00" - }, - { - "name": "symfony/string", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "0a01071610fd861cc160dfb7e2682ceec66064cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0a01071610fd861cc160dfb7e2682ceec66064cb", - "reference": "0a01071610fd861cc160dfb7e2682ceec66064cb", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "conflict": { - "symfony/translation-contracts": ">=3.0" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-01T08:32:19+00:00" - }, - { - "name": "symfony/translation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to translation", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T16:58:25+00:00" - }, - { - "name": "symfony/validator", - "version": "v4.4.48", - "source": { - "type": "git", - "url": "https://github.com/symfony/validator.git", - "reference": "54781a4c41efbd283b779110bf8ae7f263737775" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/54781a4c41efbd283b779110bf8ae7f263737775", - "reference": "54781a4c41efbd283b779110bf8ae7f263737775", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2" - }, - "conflict": { - "doctrine/lexer": "<1.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.3", - "symfony/translation": ">=5.0", - "symfony/yaml": "<3.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4", - "doctrine/cache": "^1.0|^2.0", - "egulias/email-validator": "^2.1.10|^3", - "symfony/cache": "^3.4|^4.0|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/http-foundation": "^4.1|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/intl": "^4.3|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/property-access": "^3.4|^4.0|^5.0", - "symfony/property-info": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", - "symfony/config": "", - "symfony/expression-language": "For using the Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For accessing properties within comparison constraints", - "symfony/property-info": "To automatically add NotNull and Type constraints", - "symfony/translation": "For translating validation errors.", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Validator\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to validate values", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/validator/tree/v4.4.48" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-25T13:54:11+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "2944bbc23f5f8da2b962fbcbf7c4a6109b2f4b7b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2944bbc23f5f8da2b962fbcbf7c4a6109b2f4b7b", - "reference": "2944bbc23f5f8da2b962fbcbf7c4a6109b2f4b7b", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-16T10:52:33+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v5.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "2a1d06fcf2b30829d6c01dae8e6e188424d1f8f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2a1d06fcf2b30829d6c01dae8e6e188424d1f8f6", - "reference": "2a1d06fcf2b30829d6c01dae8e6e188424d1f8f6", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-12T16:39:29+00:00" - }, - { - "name": "symfony/web-link", - "version": "v4.4.37", - "source": { - "type": "git", - "url": "https://github.com/symfony/web-link.git", - "reference": "ab13621fd0c0119ad9ebc7179be7c5a1fc6a542d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/ab13621fd0c0119ad9ebc7179be7c5a1fc6a542d", - "reference": "ab13621fd0c0119ad9ebc7179be7c5a1fc6a542d", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/link": "^1.0", - "symfony/polyfill-php72": "^1.5", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/http-kernel": "<4.3" - }, - "provide": { - "psr/link-implementation": "1.0" - }, - "require-dev": { - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.3|^5.0" - }, - "suggest": { - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\WebLink\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Manages links between resources", - "homepage": "https://symfony.com", - "keywords": [ - "dns-prefetch", - "http", - "http2", - "link", - "performance", - "prefetch", - "preload", - "prerender", - "psr13", - "push" - ], - "support": { - "source": "https://github.com/symfony/web-link/tree/v4.4.37" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:41:36+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "voku/anti-xss", - "version": "4.1.39", - "source": { - "type": "git", - "url": "https://github.com/voku/anti-xss.git", - "reference": "64a59ba4744e6722866ff3440d93561da9e85cd0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/voku/anti-xss/zipball/64a59ba4744e6722866ff3440d93561da9e85cd0", - "reference": "64a59ba4744e6722866ff3440d93561da9e85cd0", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "voku/portable-utf8": "~6.0.2" - }, - "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "voku\\helper\\": "src/voku/helper/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "EllisLab Dev Team", - "homepage": "http://ellislab.com/" - }, - { - "name": "Lars Moelleken", - "email": "lars@moelleken.org", - "homepage": "https://www.moelleken.org/" - } - ], - "description": "anti xss-library", - "homepage": "https://github.com/voku/anti-xss", - "keywords": [ - "anti-xss", - "clean", - "security", - "xss" - ], - "support": { - "issues": "https://github.com/voku/anti-xss/issues", - "source": "https://github.com/voku/anti-xss/tree/4.1.39" - }, - "funding": [ - { - "url": "https://www.paypal.me/moelleken", - "type": "custom" - }, - { - "url": "https://github.com/voku", - "type": "github" - }, - { - "url": "https://opencollective.com/anti-xss", - "type": "open_collective" - }, - { - "url": "https://www.patreon.com/voku", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/voku/anti-xss", - "type": "tidelift" - } - ], - "time": "2022-03-08T17:03:58+00:00" - }, - { - "name": "voku/portable-ascii", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", - "shasum": "" - }, - "require": { - "php": ">=7.0.0" - }, - "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" - }, - "suggest": { - "ext-intl": "Use Intl for transliterator_transliterate() support" - }, - "type": "library", - "autoload": { - "psr-4": { - "voku\\": "src/voku/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" - } - ], - "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", - "homepage": "https://github.com/voku/portable-ascii", - "keywords": [ - "ascii", - "clean", - "php" - ], - "support": { - "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" - }, - "funding": [ - { - "url": "https://www.paypal.me/moelleken", - "type": "custom" - }, - { - "url": "https://github.com/voku", - "type": "github" - }, - { - "url": "https://opencollective.com/portable-ascii", - "type": "open_collective" - }, - { - "url": "https://www.patreon.com/voku", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", - "type": "tidelift" - } - ], - "time": "2022-03-08T17:03:00+00:00" - }, - { - "name": "voku/portable-utf8", - "version": "6.0.12", - "source": { - "type": "git", - "url": "https://github.com/voku/portable-utf8.git", - "reference": "db0583727bb17666bbd2ba238c85babb973fd165" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/voku/portable-utf8/zipball/db0583727bb17666bbd2ba238c85babb973fd165", - "reference": "db0583727bb17666bbd2ba238c85babb973fd165", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "symfony/polyfill-iconv": "~1.0", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.0", - "voku/portable-ascii": "~2.0.0" - }, - "require-dev": { - "phpstan/phpstan": "1.9.*@dev", - "phpstan/phpstan-strict-rules": "1.4.*@dev", - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0", - "thecodingmachine/phpstan-strict-rules": "1.0.*@dev", - "voku/phpstan-rules": "3.1.*@dev" - }, - "suggest": { - "ext-ctype": "Use Ctype for e.g. hexadecimal digit detection", - "ext-fileinfo": "Use Fileinfo for better binary file detection", - "ext-iconv": "Use iconv for best performance", - "ext-intl": "Use Intl for best performance", - "ext-json": "Use JSON for string detection", - "ext-mbstring": "Use Mbstring for best performance" - }, - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "voku\\": "src/voku/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "(Apache-2.0 or GPL-2.0)" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Hamid Sarfraz", - "homepage": "http://pageconfig.com/" - }, - { - "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" - } - ], - "description": "Portable UTF-8 library - performance optimized (unicode) string functions for php.", - "homepage": "https://github.com/voku/portable-utf8", - "keywords": [ - "UTF", - "clean", - "php", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "issues": "https://github.com/voku/portable-utf8/issues", - "source": "https://github.com/voku/portable-utf8/tree/6.0.12" - }, - "funding": [ - { - "url": "https://www.paypal.me/moelleken", - "type": "custom" - }, - { - "url": "https://github.com/voku", - "type": "github" - }, - { - "url": "https://opencollective.com/portable-utf8", - "type": "open_collective" - }, - { - "url": "https://www.patreon.com/voku", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/voku/portable-utf8", - "type": "tidelift" - } - ], - "time": "2023-01-11T12:26:16+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - }, - { - "name": "wikimedia/less.php", - "version": "v3.1.0", - "source": { - "type": "git", - "url": "https://github.com/wikimedia/less.php.git", - "reference": "a486d78b9bd16b72f237fc6093aa56d69ce8bd13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wikimedia/less.php/zipball/a486d78b9bd16b72f237fc6093aa56d69ce8bd13", - "reference": "a486d78b9bd16b72f237fc6093aa56d69ce8bd13", - "shasum": "" - }, - "require": { - "php": ">=7.2.9" - }, - "require-dev": { - "mediawiki/mediawiki-codesniffer": "34.0.0", - "mediawiki/minus-x": "1.0.0", - "php-parallel-lint/php-console-highlighter": "0.5.0", - "php-parallel-lint/php-parallel-lint": "1.2.0", - "phpunit/phpunit": "^8.5" - }, - "bin": [ - "bin/lessc" - ], - "type": "library", - "autoload": { - "psr-0": { - "Less": "lib/" - }, - "classmap": [ - "lessc.inc.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Josh Schmidt", - "homepage": "https://github.com/oyejorge" - }, - { - "name": "Matt Agar", - "homepage": "https://github.com/agar" - }, - { - "name": "Martin Jantošovič", - "homepage": "https://github.com/Mordred" - } - ], - "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)", - "keywords": [ - "css", - "less", - "less.js", - "lesscss", - "php", - "stylesheet" - ], - "support": { - "issues": "https://github.com/wikimedia/less.php/issues", - "source": "https://github.com/wikimedia/less.php/tree/v3.1.0" - }, - "time": "2020-12-11T19:33:31+00:00" - } - ], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": [], - "prefer-stable": true, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/tools/deploy.sh b/tools/deploy.sh index 986bb725..e74d6784 100755 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -19,8 +19,6 @@ echo "Removing unnecessary files from final release archive..." rm -fR /tmp/Adyen/deploy/AdyenPayment/tests rm -fR /tmp/Adyen/deploy/AdyenPayment/tools rm -fR /tmp/Adyen/deploy/AdyenPayment/PluginInstallation -rm -fR /tmp/Adyen/deploy/AdyenPayment/vendor/monolog -rm -fR /tmp/Adyen/deploy/AdyenPayment/vendor/psr rm -fR /tmp/Adyen/deploy/AdyenPayment/.git rm -fR /tmp/Adyen/deploy/AdyenPayment/.idea rm -fR /tmp/Adyen/deploy/AdyenPayment/.github @@ -45,4 +43,4 @@ php tools/sw.phar plugin:zip:dir -q /tmp/Adyen/deploy/AdyenPayment/ rm -fR /tmp/Adyen mv AdyenPayment.zip ./tools/AdyenPayment.zip -echo "New plugin archive for version $version created: $PWD/tools/AdyenPayment.zip" +echo "New plugin archive for version $version created: $PWD/tools/AdyenPayment.zip" \ No newline at end of file diff --git a/tools/prerelease/CS-3947/AdyenPayment.zip b/tools/prerelease/CS-3947/AdyenPayment.zip deleted file mode 100644 index 9c205aca..00000000 Binary files a/tools/prerelease/CS-3947/AdyenPayment.zip and /dev/null differ diff --git a/tools/prerelease/CS-4030/AdyenPayment.zip b/tools/prerelease/CS-4030/AdyenPayment.zip deleted file mode 100644 index df4efb4a..00000000 Binary files a/tools/prerelease/CS-4030/AdyenPayment.zip and /dev/null differ diff --git a/tools/sw.phar b/tools/sw.phar index 5b73f4af..015fc45d 100644 Binary files a/tools/sw.phar and b/tools/sw.phar differ