From ca146e97f009c1f54ff0d0f1c6fc989171516f46 Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Mon, 15 Jun 2020 16:42:58 +0200 Subject: [PATCH 1/9] Initial commit --- .github/CODEOWNERS | 1 + .gitignore | 7 + AdyenPayment.php | 238 +++++++++ Commands/ProcessNotifications.php | 94 ++++ Components/Adyen/ApiFactory.php | 86 ++++ Components/Adyen/OriginKeysService.php | 44 ++ Components/Adyen/PaymentMethodService.php | 119 +++++ Components/Adyen/RefundService.php | 82 ++++ Components/BasketService.php | 250 ++++++++++ Components/Builder/NotificationBuilder.php | 99 ++++ .../Calculator/PriceCalculationService.php | 22 + .../NotificationProcessorCompilerPass.php | 29 ++ Components/Configuration.php | 192 ++++++++ Components/DataConversion.php | 21 + Components/FifoNotificationLoader.php | 48 ++ Components/IncomingNotificationManager.php | 71 +++ Components/Manager/AdyenManager.php | 62 +++ Components/NotificationManager.php | 80 +++ Components/NotificationProcessor.php | 172 +++++++ .../NotificationProcessor/Authorisation.php | 86 ++++ .../NotificationProcessor/Cancellation.php | 88 ++++ Components/NotificationProcessor/Capture.php | 92 ++++ .../NotificationProcessor/CaptureFailed.php | 69 +++ .../NotificationProcessor/Chargeback.php | 90 ++++ .../ChargebackReversed.php | 90 ++++ .../NotificationProcessorInterface.php | 34 ++ Components/NotificationProcessor/Refund.php | 85 ++++ .../NotificationProcessor/RefundFailed.php | 80 +++ .../RefundedReversed.php | 83 ++++ Components/OriginKeysService.php | 127 +++++ Components/Payload/Chain.php | 40 ++ Components/Payload/PaymentContext.php | 132 +++++ Components/Payload/PaymentPayloadProvider.php | 18 + .../Providers/ApplicationInfoProvider.php | 72 +++ .../Payload/Providers/BrowserInfoProvider.php | 28 ++ .../Providers/LineItemsInfoProvider.php | 115 +++++ .../Payload/Providers/OrderInfoProvider.php | 32 ++ .../Providers/PaymentMethodProvider.php | 24 + .../Payload/Providers/ShopperInfoProvider.php | 40 ++ Components/PaymentMethodService.php | 232 +++++++++ Components/PaymentStatusUpdate.php | 67 +++ Components/ShopwareVersionCheck.php | 58 +++ ...enPaymentNotificationsListingExtension.php | 69 +++ Controllers/Backend/AdyenPaymentRefund.php | 25 + Controllers/Backend/TestAdyenApi.php | 27 ++ Controllers/Frontend/Adyen.php | 268 ++++++++++ Controllers/Frontend/Notification.php | 164 +++++++ Controllers/Frontend/Process.php | 158 ++++++ Exceptions/InvalidParameterException.php | 15 + .../NoNotificationProcessorFoundException.php | 21 + Exceptions/OrderNotFoundException.php | 20 + Models/Enum/Channel.php | 12 + Models/Enum/NotificationStatus.php | 24 + Models/Enum/PaymentResultCodes.php | 18 + Models/Event.php | 29 ++ Models/Feedback/NotificationItemFeedback.php | 69 +++ .../NotificationProcessorFeedback.php | 97 ++++ Models/Notification.php | 389 +++++++++++++++ Models/NotificationException.php | 39 ++ Models/PaymentInfo.php | 198 ++++++++ Models/PaymentMethodInfo.php | 65 +++ Models/Refund.php | 162 +++++++ Models/ShopwareInfo.php | 52 ++ README.md | 44 ++ Resources/config.xml | 108 +++++ Resources/cronjob.xml | 11 + .../js/jquery.adyen-checkout-error.js | 114 +++++ .../frontend/js/jquery.adyen-confirm-order.js | 319 ++++++++++++ .../frontend/js/jquery.adyen-finish-order.js | 23 + .../js/jquery.adyen-payment-selection.js | 299 ++++++++++++ Resources/frontend/js/jquery.plugin-loader.js | 10 + Resources/frontend/less/all.less | 34 ++ Resources/menu.xml | 14 + Resources/services.xml | 13 + Resources/services/commands.xml | 13 + Resources/services/components.xml | 144 ++++++ Resources/services/cronjobs.xml | 14 + Resources/services/loggers.xml | 40 ++ Resources/services/managers.xml | 21 + Resources/services/subscribers.xml | 64 +++ Resources/services/version/563.xml | 10 + .../app.js | 29 ++ .../controller/main.js | 11 + .../model/notification.js | 41 ++ .../store/notification.js | 11 + .../list/extensions/notification_filter.js | 59 +++ .../view/list/notification.js | 93 ++++ .../view/list/window.js | 18 + .../views/backend/adyen_payment_order/app.js | 10 + .../adyen_payment_order/model/order.js | 6 + .../view/detail/tabs/notifications.js | 53 ++ .../view/detail/tabs/notifications/detail.js | 41 ++ .../view/detail/tabs/notifications/list.js | 41 ++ .../view/detail/tabs/refunds.js | 29 ++ .../view/detail/tabs/refunds/detail.js | 96 ++++ .../view/detail/transaction_details.js | 119 +++++ .../view/detail/transaction_tabs.js | 50 ++ .../adyen_payment_order/view/detail/window.js | 79 +++ .../customer/adyen_payment_method/app.js | 4 + .../adyen_payment_method/view/list.js | 22 + .../backend/order/adyen_payment_method/app.js | 4 + .../order/adyen_payment_method/view/list.js | 22 + .../frontend/checkout/adyen_libaries.tpl | 13 + .../frontend/checkout/change_payment.tpl | 26 + Resources/views/frontend/checkout/confirm.tpl | 49 ++ Subscriber/AccountPaymentSubscriber.php | 99 ++++ Subscriber/BackendConfigSubscriber.php | 77 +++ Subscriber/BackendJavascriptSubscriber.php | 110 +++++ Subscriber/BackendOrderSubscriber.php | 110 +++++ Subscriber/BackendPaymentSubscriber.php | 53 ++ Subscriber/CheckoutSubscriber.php | 459 ++++++++++++++++++ Subscriber/CookieSubscriber.php | 32 ++ Subscriber/Cronjob/ProcessNotifications.php | 74 +++ Subscriber/FrontendPaymentNameSubscriber.php | 136 ++++++ .../Notification/LogIncomingNotification.php | 52 ++ Subscriber/PaymentSubscriber.php | 117 +++++ Subscriber/Template.php | 69 +++ composer.json | 35 ++ plugin.png | Bin 0 -> 592 bytes plugin.xml | 26 + ruleset.xml | 6 + 121 files changed, 9096 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .gitignore create mode 100644 AdyenPayment.php create mode 100644 Commands/ProcessNotifications.php create mode 100644 Components/Adyen/ApiFactory.php create mode 100644 Components/Adyen/OriginKeysService.php create mode 100644 Components/Adyen/PaymentMethodService.php create mode 100644 Components/Adyen/RefundService.php create mode 100644 Components/BasketService.php create mode 100644 Components/Builder/NotificationBuilder.php create mode 100644 Components/Calculator/PriceCalculationService.php create mode 100644 Components/CompilerPass/NotificationProcessorCompilerPass.php create mode 100644 Components/Configuration.php create mode 100644 Components/DataConversion.php create mode 100644 Components/FifoNotificationLoader.php create mode 100644 Components/IncomingNotificationManager.php create mode 100644 Components/Manager/AdyenManager.php create mode 100644 Components/NotificationManager.php create mode 100644 Components/NotificationProcessor.php create mode 100644 Components/NotificationProcessor/Authorisation.php create mode 100644 Components/NotificationProcessor/Cancellation.php create mode 100644 Components/NotificationProcessor/Capture.php create mode 100644 Components/NotificationProcessor/CaptureFailed.php create mode 100644 Components/NotificationProcessor/Chargeback.php create mode 100644 Components/NotificationProcessor/ChargebackReversed.php create mode 100644 Components/NotificationProcessor/NotificationProcessorInterface.php create mode 100644 Components/NotificationProcessor/Refund.php create mode 100644 Components/NotificationProcessor/RefundFailed.php create mode 100644 Components/NotificationProcessor/RefundedReversed.php create mode 100644 Components/OriginKeysService.php create mode 100644 Components/Payload/Chain.php create mode 100644 Components/Payload/PaymentContext.php create mode 100644 Components/Payload/PaymentPayloadProvider.php create mode 100644 Components/Payload/Providers/ApplicationInfoProvider.php create mode 100644 Components/Payload/Providers/BrowserInfoProvider.php create mode 100644 Components/Payload/Providers/LineItemsInfoProvider.php create mode 100644 Components/Payload/Providers/OrderInfoProvider.php create mode 100644 Components/Payload/Providers/PaymentMethodProvider.php create mode 100644 Components/Payload/Providers/ShopperInfoProvider.php create mode 100644 Components/PaymentMethodService.php create mode 100644 Components/PaymentStatusUpdate.php create mode 100644 Components/ShopwareVersionCheck.php create mode 100644 Controllers/Backend/AdyenPaymentNotificationsListingExtension.php create mode 100644 Controllers/Backend/AdyenPaymentRefund.php create mode 100644 Controllers/Backend/TestAdyenApi.php create mode 100644 Controllers/Frontend/Adyen.php create mode 100644 Controllers/Frontend/Notification.php create mode 100644 Controllers/Frontend/Process.php create mode 100644 Exceptions/InvalidParameterException.php create mode 100644 Exceptions/NoNotificationProcessorFoundException.php create mode 100644 Exceptions/OrderNotFoundException.php create mode 100644 Models/Enum/Channel.php create mode 100644 Models/Enum/NotificationStatus.php create mode 100644 Models/Enum/PaymentResultCodes.php create mode 100644 Models/Event.php create mode 100644 Models/Feedback/NotificationItemFeedback.php create mode 100644 Models/Feedback/NotificationProcessorFeedback.php create mode 100644 Models/Notification.php create mode 100644 Models/NotificationException.php create mode 100644 Models/PaymentInfo.php create mode 100644 Models/PaymentMethodInfo.php create mode 100644 Models/Refund.php create mode 100644 Models/ShopwareInfo.php create mode 100644 README.md create mode 100755 Resources/config.xml create mode 100644 Resources/cronjob.xml create mode 100644 Resources/frontend/js/jquery.adyen-checkout-error.js create mode 100644 Resources/frontend/js/jquery.adyen-confirm-order.js create mode 100644 Resources/frontend/js/jquery.adyen-finish-order.js create mode 100644 Resources/frontend/js/jquery.adyen-payment-selection.js create mode 100644 Resources/frontend/js/jquery.plugin-loader.js create mode 100644 Resources/frontend/less/all.less create mode 100644 Resources/menu.xml create mode 100644 Resources/services.xml create mode 100644 Resources/services/commands.xml create mode 100644 Resources/services/components.xml create mode 100644 Resources/services/cronjobs.xml create mode 100644 Resources/services/loggers.xml create mode 100644 Resources/services/managers.xml create mode 100644 Resources/services/subscribers.xml create mode 100644 Resources/services/version/563.xml create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/app.js create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/controller/main.js create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/store/notification.js create mode 100644 Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/extensions/notification_filter.js create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/notification.js create mode 100755 Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/window.js create mode 100644 Resources/views/backend/adyen_payment_order/app.js create mode 100644 Resources/views/backend/adyen_payment_order/model/order.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/detail.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/list.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds/detail.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/transaction_details.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/transaction_tabs.js create mode 100644 Resources/views/backend/adyen_payment_order/view/detail/window.js create mode 100644 Resources/views/backend/customer/adyen_payment_method/app.js create mode 100644 Resources/views/backend/customer/adyen_payment_method/view/list.js create mode 100644 Resources/views/backend/order/adyen_payment_method/app.js create mode 100644 Resources/views/backend/order/adyen_payment_method/view/list.js create mode 100644 Resources/views/frontend/checkout/adyen_libaries.tpl create mode 100644 Resources/views/frontend/checkout/change_payment.tpl create mode 100644 Resources/views/frontend/checkout/confirm.tpl create mode 100644 Subscriber/AccountPaymentSubscriber.php create mode 100644 Subscriber/BackendConfigSubscriber.php create mode 100644 Subscriber/BackendJavascriptSubscriber.php create mode 100644 Subscriber/BackendOrderSubscriber.php create mode 100644 Subscriber/BackendPaymentSubscriber.php create mode 100644 Subscriber/CheckoutSubscriber.php create mode 100644 Subscriber/CookieSubscriber.php create mode 100644 Subscriber/Cronjob/ProcessNotifications.php create mode 100644 Subscriber/FrontendPaymentNameSubscriber.php create mode 100644 Subscriber/Notification/LogIncomingNotification.php create mode 100755 Subscriber/PaymentSubscriber.php create mode 100644 Subscriber/Template.php create mode 100644 composer.json create mode 100644 plugin.png create mode 100644 plugin.xml create mode 100644 ruleset.xml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..a0d49593 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @cyattilakiss @acampos1916 @msilvagarcia \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..058cce53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +#development +.idea/ +*.iml +.DS_Store + +#composer +vendor/ \ No newline at end of file diff --git a/AdyenPayment.php b/AdyenPayment.php new file mode 100644 index 00000000..c6c8ab90 --- /dev/null +++ b/AdyenPayment.php @@ -0,0 +1,238 @@ +addCompilerPass(new NotificationProcessorCompilerPass()); + + parent::build($container); + + //set default logger level for 5.4 + if (!$container->hasParameter('kernel.default_error_level')) { + $container->setParameter('kernel.default_error_level', Logger::ERROR); + } + + $loader = new XmlFileLoader( + $container, + new FileLocator() + ); + + $loader->load(__DIR__ . '/Resources/services.xml'); + + $versionCheck = $container->get('adyen_payment.components.shopware_version_check'); + + if ($versionCheck->isHigherThanShopwareVersion('v5.6.2')) { + $loader->load(__DIR__ . '/Resources/services/version/563.xml'); + } + } + + /** + * @param InstallContext $context + * @throws Exception + */ + public function install(InstallContext $context) + { + $this->installAttributes(); + + $tool = new SchemaTool($this->container->get('models')); + $classes = $this->getModelMetaData(); + $tool->updateSchema($classes, true); + } + + /** + * @param UninstallContext $context + * @throws Exception + */ + public function uninstall(UninstallContext $context) + { + $this->deactivatePaymentMethods(); + + if (!$context->keepUserData()) { + $this->uninstallAttributes($context); + + $tool = new SchemaTool($this->container->get('models')); + $classes = $this->getModelMetaData(); + $tool->dropSchema($classes); + } + + if ($context->getPlugin()->getActive()) { + $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + } + } + + /** + * @param DeactivateContext $context + */ + public function deactivate(DeactivateContext $context) + { + $this->deactivatePaymentMethods(); + + $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + } + + /** + * @param ActivateContext $context + */ + public function activate(ActivateContext $context) + { + /** @var PaymentInstaller $installer */ + $installer = $this->container->get('shopware.plugin_payment_installer'); + + $paymentOptions[] = $this->getPaymentOptions(); + + foreach ($paymentOptions as $key => $options) { + $installer->createOrUpdate($context->getPlugin(), $options); + } + + $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); + } + + /** + * Deactivate all Adyen payment methods + */ + private function deactivatePaymentMethods() + { + $em = $this->container->get('models'); + $qb = $em->createQueryBuilder(); + + $query = $qb->update('Shopware\Models\Payment\Payment', 'p') + ->set('p.active', '?1') + ->where($qb->expr()->like('p.name', '?2')) + ->setParameter(1, false) + ->setParameter(2, self::ADYEN_GENERAL_PAYMENT_METHOD) + ->getQuery(); + + $query->execute(); + } + + /** + * @param UninstallContext $uninstallContext + * @throws Exception + */ + private function uninstallAttributes(UninstallContext $uninstallContext) + { + $crudService = $this->container->get('shopware_attribute.crud_service'); + $crudService->delete('s_user_attributes', self::ADYEN_PAYMENT_PAYMENT_METHOD); + + $this->rebuildAttributeModels(); + } + + /** + * @throws Exception + */ + private function installAttributes() + { + $crudService = $this->container->get('shopware_attribute.crud_service'); + $crudService->update( + 's_user_attributes', + self::ADYEN_PAYMENT_PAYMENT_METHOD, + TypeMapping::TYPE_STRING, + [ + 'displayInBackend' => true, + 'label' => 'Adyen Payment Method' + ] + ); + + $this->rebuildAttributeModels(); + } + + /** + * @return array + */ + private function getModelMetaData(): array + { + $entityManager = $this->container->get('models'); + + return [ + $entityManager->getClassMetadata(Notification::class), + $entityManager->getClassMetadata(PaymentInfo::class), + $entityManager->getClassMetadata(Refund::class), + ]; + } + + /** + * @return array + */ + private function getPaymentOptions() + { + $options = [ + 'name' => self::ADYEN_GENERAL_PAYMENT_METHOD, + 'description' => 'Adyen payment methods', + 'action' => null, + 'active' => 1, + 'position' => 0, + 'additionalDescription' => '' + ]; + + return $options; + } + + private function rebuildAttributeModels() + { + /** @var Cache $metaDataCache */ + $metaDataCache = $this->container->get('models')->getConfiguration()->getMetadataCacheImpl(); + if ($metaDataCache) { + $metaDataCache->deleteAll(); + } + + $this->container->get('models')->generateAttributeModels(['s_user_attributes']); + } +} + +if (AdyenPayment::isPackage()) { + require_once AdyenPayment::getPackageVendorAutoload(); +} diff --git a/Commands/ProcessNotifications.php b/Commands/ProcessNotifications.php new file mode 100644 index 00000000..3f8c1c86 --- /dev/null +++ b/Commands/ProcessNotifications.php @@ -0,0 +1,94 @@ +loader = $fifoNotificationLoader; + $this->notificationProcessor = $notificationProcessor; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('adyen:payment:process:notifications') + ->setDescription('Process notifications in queue') + ->addOption( + 'number', + 'no', + \Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL, + 'Number of notifications to process. Defaults to ' . + ProcessNotificationsCronjob::NUMBER_OF_NOTIFICATIONS_TO_HANDLE . '.', + ProcessNotificationsCronjob::NUMBER_OF_NOTIFICATIONS_TO_HANDLE + ); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int|void|null + * @throws \Doctrine\ORM\ORMException + * @throws \Enlight_Event_Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $number = $input->getOption('number'); + + /** @var \Generator $feedback */ + $feedback = $this->notificationProcessor->processMany( + $this->loader->load($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/ApiFactory.php b/Components/Adyen/ApiFactory.php new file mode 100644 index 00000000..8983f801 --- /dev/null +++ b/Components/Adyen/ApiFactory.php @@ -0,0 +1,86 @@ +configuration = $configuration; + $this->logger = $logger; + $this->shopRepository = $modelManager->getRepository(Shop::class); + $this->apiClients = []; + } + + /** + * @param null|Shop $shop + * @return Client + * @throws AdyenException + */ + public function create($shop = null) + { + if (!$shop) { + $shop = $this->shopRepository->getDefault(); + } + + if (!$this->apiClients[$shop->getId()]) { + $urlPrefix = null; + if ($this->configuration->getEnvironment($shop) === Environment::LIVE) { + $urlPrefix = $this->configuration->getApiUrlPrefix($shop); + } + + $apiClient = new Client(); + $apiClient->setXApiKey($this->configuration->getApiKey($shop)); + $apiClient->setEnvironment($this->configuration->getEnvironment($shop), $urlPrefix); + $apiClient->setLogger($this->logger); + + $this->apiClients[$shop->getId()] = $apiClient; + } + + return $this->apiClients[$shop->getId()]; + } +} diff --git a/Components/Adyen/OriginKeysService.php b/Components/Adyen/OriginKeysService.php new file mode 100644 index 00000000..298a97a8 --- /dev/null +++ b/Components/Adyen/OriginKeysService.php @@ -0,0 +1,44 @@ +getRepository(Shop::class)->findOneBy(['default' => 1]); + $this->checkoutUtility = new CheckoutUtility($apiFactory->create($mainShop)); + } + + /** + * @param array $originDomains + * @return mixed + * @throws AdyenException + */ + public function generate(array $originDomains) + { + return $this->checkoutUtility->originKeys(['originDomains' => $originDomains])['originKeys']; + } +} diff --git a/Components/Adyen/PaymentMethodService.php b/Components/Adyen/PaymentMethodService.php new file mode 100644 index 00000000..1becd1ec --- /dev/null +++ b/Components/Adyen/PaymentMethodService.php @@ -0,0 +1,119 @@ +apiClient = $apiFactory->create(); + $this->configuration = $configuration; + $this->logger = $logger; + } + + /** + * @param string $countryCode + * @param string $currency + * @param int $value + * @param bool $cache + * @return array + * @throws AdyenException + */ + public function getPaymentMethods( + $countryCode = null, + $currency = null, + $locale = null, + $value = null, + $cache = true + ): array { + $cacheKey = $this->getCacheKey($countryCode ?? '', $currency ?? '', (string)$value ?? ''); + if ($cache && isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $checkout = new Checkout($this->apiClient); + $adyenCurrency = new Currency(); + + $requestParams = [ + 'merchantAccount' => $this->configuration->getMerchantAccount(), + 'countryCode' => $countryCode, + 'amount' => [ + 'currency' => $currency, + 'value' => $adyenCurrency->sanitize($value, $currency), + ], + 'channel' => Channel::WEB, + 'shopperLocale' => $locale ?? Shopware()->Shop()->getLocale()->getLocale(), + ]; + + try { + $paymentMethods = $checkout->paymentMethods($requestParams); + } catch (AdyenException $e) { + $this->logger->critical($e); + return []; + } + + if ($cache) { + $this->cache[$cacheKey] = $paymentMethods; + } + + return $paymentMethods; + } + + /** + * @param string ...$keys + * @return string + */ + private function getCacheKey(string ...$keys) + { + return md5(implode(',', $keys)); + } + + /** + * @return Checkout + * @throws AdyenException + */ + public function getCheckout() + { + return new Checkout($this->apiClient); + } +} diff --git a/Components/Adyen/RefundService.php b/Components/Adyen/RefundService.php new file mode 100644 index 00000000..d274bbc1 --- /dev/null +++ b/Components/Adyen/RefundService.php @@ -0,0 +1,82 @@ +apiFactory = $apiFactory; + $this->modelManager = $modelManager; + $this->notificationManager = $notificationManager; + } + + /** + * @param int $orderId + * @return Refund + * @throws AdyenException + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function doRefund(int $orderId): Refund + { + $order = $this->modelManager->find(Order::class, $orderId); + $apiClient = $this->apiFactory->create($order->getShop()); + $modification = new Modification($apiClient); + + $notification = $this->notificationManager->getLastNotificationForOrderId($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; + } +} diff --git a/Components/BasketService.php b/Components/BasketService.php new file mode 100644 index 00000000..309b4546 --- /dev/null +++ b/Components/BasketService.php @@ -0,0 +1,250 @@ +sBasket = Shopware()->Modules()->Basket(); + $this->events = $events; + $this->modelManager = $modelManager; + + $this->statusRepository = $modelManager->getRepository(Status::class); + $this->orderRepository = $modelManager->getRepository(Order::class); + $this->voucherRepository = $modelManager->getRepository(Voucher::class); + $this->voucherCodeRepository = $modelManager->getRepository(Code::class); + } + + /** + * @param int $orderNumber + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Enlight_Event_Exception + * @throws \Enlight_Exception + * @throws \Zend_Db_Adapter_Exception + */ + public function cancelAndRestoreByOrderNumber(int $orderNumber) + { + $order = $this->getOrderByOrderNumber($orderNumber); + if (!$order) { + return; + } + + $this->restoreFromOrder($order); + $this->cancelOrder($order); + } + + /** + * @param int $orderNumber + * @return Order|null + */ + public function getOrderByOrderNumber(int $orderNumber) + { + return $this->orderRepository->findOneBy(['number' => $orderNumber]); + } + + /** + * @param Order $order + * @throws \Enlight_Event_Exception + * @throws \Enlight_Exception + * @throws \Zend_Db_Adapter_Exception + */ + public function restoreFromOrder(Order $order) + { + $this->sBasket->sDeleteBasket(); + $orderDetails = $order->getDetails(); + foreach ($orderDetails as $orderDetail) { + $this->processOrderDetail($order, $orderDetail); + } + $this->sBasket->sRefreshBasket(); + } + + /** + * @param Order $order + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function cancelOrder(Order $order) + { + /** @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); + } + + /** + * @param Order $order + * @param Detail $orderDetail + * @throws \Enlight_Event_Exception + */ + private function processOrderDetail(Order $order, Detail $orderDetail) + { + $orderDetailFiltered = $this->events->filter(Event::BASKET_BEFORE_PROCESS_ORDER_DETAIL, $orderDetail, [ + 'order' => $order, + 'orderDetail' => $orderDetail + ]); + + if (!$orderDetailFiltered || !$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: + $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: + case self::MODE_SURCHARGE_DISCOUNT: + break; + } + + $this->events->notify(Event::BASKET_AFTER_PROCESS_ORDER_DETAIL, [ + 'order' => $order, + 'orderDetail' => $orderDetailFiltered, + 'originalOrderDetail' => $orderDetail + ]); + } + + /** + * @param Detail $orderDetail + * @throws \Enlight_Event_Exception + * @throws \Enlight_Exception + * @throws \Zend_Db_Adapter_Exception + */ + private function addArticle(Detail $orderDetail) + { + $this->sBasket->sAddArticle( + $orderDetail->getArticleNumber(), + $orderDetail->getQuantity() + ); + } + + /** + * @param Detail $orderDetail + * @throws \Zend_Db_Adapter_Exception + */ + private function addPremium(Detail $orderDetail) + { + Shopware()->Front()->Request()->setQuery('sAddPremium', $orderDetail->getArticleNumber()); + $this->sBasket->sInsertPremium(); + } + + /** + * @param Detail $orderDetail + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Enlight_Event_Exception + * @throws \Enlight_Exception + * @throws \Zend_Db_Adapter_Exception + */ + private function addVoucher(Detail $orderDetail) + { + if (!$orderDetail || !$orderDetail instanceof Detail) { + return; + } + /** @var Voucher $voucher */ + $voucher = $this->voucherRepository->findOneBy(['orderCode' => $orderDetail->getArticleNumber()]); + + if (!$voucher) { + return; + } + + $voucherCode = $voucher->getVoucherCode(); + + if ($voucher->getModus() === 1) { + /** @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 new file mode 100644 index 00000000..7c9fd88a --- /dev/null +++ b/Components/Builder/NotificationBuilder.php @@ -0,0 +1,99 @@ +modelManager = $modelManager; + $this->orderRepository = $modelManager->getRepository(Order::class); + $this->currency = new Currency(); + } + + /** + * Builds Notification object from Adyen webhook params + * + * @param $params + * @return Notification|void + * @throws OrderNotFoundException + * @throws InvalidParameterException + */ + public function fromParams($params) + { + $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); + + 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['success'])) { + $notification->setSuccess($params['success'] == 'true'); + } + if (isset($params['merchantAccountCode'])) { + $notification->setMerchantAccountCode($params['merchantAccountCode']); + } + if (isset($params['amount']['value']) && isset($params['amount']['currency'])) { + $notification->setAmountValue($params['amount']['value']); + $notification->setAmountCurrency($params['amount']['currency']); + } + if (isset($params['reason'])) { + $notification->setErrorDetails($params['reason']); + } + + return $notification; + } +} diff --git a/Components/Calculator/PriceCalculationService.php b/Components/Calculator/PriceCalculationService.php new file mode 100644 index 00000000..e2afa18f --- /dev/null +++ b/Components/Calculator/PriceCalculationService.php @@ -0,0 +1,22 @@ +getAmountExcludingTax($amount, $tax), 2); + } +} diff --git a/Components/CompilerPass/NotificationProcessorCompilerPass.php b/Components/CompilerPass/NotificationProcessorCompilerPass.php new file mode 100644 index 00000000..c7a5e2e4 --- /dev/null +++ b/Components/CompilerPass/NotificationProcessorCompilerPass.php @@ -0,0 +1,29 @@ +getDefinition('adyen_payment.components.notification_processor'); + $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 new file mode 100644 index 00000000..1cb045bf --- /dev/null +++ b/Components/Configuration.php @@ -0,0 +1,192 @@ +cachedConfigReader = $cachedConfigReader; + $this->connection = $connection; + } + + /** + * @param bool $shop + * @return string + */ + public function getEnvironment($shop = false): string + { + if ($this->getConfig('environment', $shop) === self::ENV_LIVE) { + return Environment::LIVE; + } + + return Environment::TEST; + } + + /** + * @param bool $shop + * @return bool + */ + public function isTestModus($shop = false): bool + { + return $this->getEnvironment($shop) === Environment::TEST; + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getMerchantAccount($shop = false): string + { + return (string)$this->getConfig('merchant_account', $shop); + } + + /** + * @param string $key + * @param bool|Shop $shop + * @return mixed + */ + public function getConfig($key = null, $shop = false) + { + if (!$shop) { + $shop = null; + } + + $config = $this->cachedConfigReader->getByPluginName(AdyenPayment::NAME, $shop); + + if ($key === null) { + return $config; + } + + if (array_key_exists($key, $config)) { + return $config[$key]; + } + + return null; + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getJsComponents3DS2ChallengeImageSize($shop = false): string + { + return (string)$this->getConfig('js_components_3DS2_challenge_image_size', $shop); + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getApiKey($shop = false): string + { + return (string)$this->getConfig('api_key', $shop); + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getApiUrlPrefix($shop = false): string + { + return (string)$this->getConfig('api_url_prefix', $shop); + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getOriginKey($shop = false): string + { + return (string)$this->getConfig('origin_key', $shop); + } + + /** + * @param bool|Shop $shop + * @return string + */ + public function getNotificationHmac($shop = false): string + { + return (string)$this->getConfig('notification_hmac', $shop); + } + + /** + * @param bool $shop + * @return string + */ + public function getNotificationAuthUsername($shop = false): string + { + return (string)$this->getConfig('notification_auth_username', $shop); + } + + /** + * @param bool $shop + * @return string + */ + public function getNotificationAuthPassword($shop = false): string + { + return (string)$this->getConfig('notification_auth_password', $shop); + } + + /** + * @param bool $shop + * @return string + */ + public function getGoogleMerchantId($shop = false): string + { + return (string)$this->getConfig('google_merchant_id', $shop); + } + + /** + * @return string + */ + public function getPaymentMethodPrefix(): string + { + return (string)self::PAYMENT_PREFIX; + } + + /** + * @return int + * @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/DataConversion.php b/Components/DataConversion.php new file mode 100644 index 00000000..9a234e3a --- /dev/null +++ b/Components/DataConversion.php @@ -0,0 +1,21 @@ +notificationManager = $notificationManager; + } + + /** + * @param int $amount + * @return \Generator + */ + 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/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php new file mode 100644 index 00000000..9a1d6055 --- /dev/null +++ b/Components/IncomingNotificationManager.php @@ -0,0 +1,71 @@ +logger = $logger; + $this->notificationBuilder = $notificationBuilder; + $this->entityManager = $entityManager; + } + + /** + * @param array $notificationItems + * @return \Generator + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function save(array $notificationItems) + { + foreach ($notificationItems as $notificationItem) { + try { + $notification = $this->notificationBuilder->fromParams($notificationItem['NotificationRequestItem']); + $this->entityManager->persist($notification); + } catch (InvalidParameterException $exception) { + $this->logger->warning($exception->getMessage()); + yield new NotificationItemFeedback($exception->getMessage(), $notificationItem); + } catch (OrderNotFoundException $exception) { + $this->logger->warning($exception->getMessage()); + yield new NotificationItemFeedback($exception->getMessage(), $notificationItem); + } + } + $this->entityManager->flush(); + } +} diff --git a/Components/Manager/AdyenManager.php b/Components/Manager/AdyenManager.php new file mode 100644 index 00000000..43ffaf97 --- /dev/null +++ b/Components/Manager/AdyenManager.php @@ -0,0 +1,62 @@ +modelManager = $modelManager; + $this->session = $session; + } + + /** + * @param $paymentData + */ + public function storePaymentDataInSession($paymentData) + { + $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_PAYMENT_DATA, $paymentData); + } + + /** + * @return string + */ + public function getPaymentDataSession(): string + { + return $this->session->offsetGet(AdyenPayment::SESSION_ADYEN_PAYMENT_DATA) ?? ''; + } + + public function unsetPaymentDataInSession() + { + $this->session->offsetUnset(AdyenPayment::SESSION_ADYEN_PAYMENT); + $this->session->offsetUnset(AdyenPayment::SESSION_ADYEN_PAYMENT_VALID); + $this->session->offsetUnset(AdyenPayment::SESSION_ADYEN_PAYMENT_DATA); + } + + public function unsetValidPaymentSession() + { + $this->session->offsetUnset(AdyenPayment::SESSION_ADYEN_PAYMENT_VALID); + } +} diff --git a/Components/NotificationManager.php b/Components/NotificationManager.php new file mode 100644 index 00000000..931f62b3 --- /dev/null +++ b/Components/NotificationManager.php @@ -0,0 +1,80 @@ +modelManager = $modelManager; + $this->notificationRepository = $modelManager->getRepository(Notification::class); + } + + /** + * @return mixed + * @throws NoResultException + * @throws NonUniqueResultException + */ + public function getNextNotificationToHandle() + { + $builder = $this->notificationRepository->createQueryBuilder('n'); + $builder->where("n.status = :statusReceived OR n.status = :statusRetry") + ->orderBy('n.updatedAt', 'ASC') + ->setParameter('statusReceived', NotificationStatus::STATUS_RECEIVED) + ->setParameter('statusRetry', NotificationStatus::STATUS_RETRY) + ->setMaxResults(1); + + return $builder->getQuery()->getSingleResult(); + } + + /** + * @param int $orderId + * @return mixed|null + * @throws NonUniqueResultException + */ + 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 null; + } + } +} diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php new file mode 100644 index 00000000..f8dbb4aa --- /dev/null +++ b/Components/NotificationProcessor.php @@ -0,0 +1,172 @@ +logger = $logger; + $this->modelManager = $modelManager; + $this->eventManager = $eventManager; + } + + /** + * @param Traversable $notifications + * @return \Generator + * @throws \Doctrine\ORM\ORMException + * @throws \Enlight_Event_Exception + */ + public function processMany(Traversable $notifications) + { + foreach ($notifications as $notification) { + try { + yield from $this->process($notification); + } 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); + } + } + } + + /** + * @param Notification $notification + * @throws NoNotificationProcessorFoundException + * @throws OrderNotFoundException + * @throws \Doctrine\ORM\ORMException + * @throws \Enlight_Event_Exception + */ + private function process(Notification $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' => $exception, + '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); + } + + /** + * @param NotificationProcessorInterface $processor + */ + public function addProcessor(NotificationProcessorInterface $processor) + { + $this->processors[] = $processor; + } + + /** + * Finds all processors that support this type of Notification + * + * @param $notification + * @return array + */ + private function findProcessors($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 new file mode 100644 index 00000000..3757c81a --- /dev/null +++ b/Components/NotificationProcessor/Authorisation.php @@ -0,0 +1,86 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $order = $notification->getOrder(); + $this->eventManager->notify( + Event::NOTIFICATION_PROCESS_AUTHORISATION, + [ + 'order' => $order, + 'notification' => $notification + ] + ); + + $status = $notification->isSuccess() ? + Status::PAYMENT_STATE_THE_CREDIT_HAS_BEEN_ACCEPTED : + Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED; + + $this->paymentStatusUpdate->updatePaymentStatus($order, $status); + } +} diff --git a/Components/NotificationProcessor/Cancellation.php b/Components/NotificationProcessor/Cancellation.php new file mode 100644 index 00000000..4c186b9a --- /dev/null +++ b/Components/NotificationProcessor/Cancellation.php @@ -0,0 +1,88 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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 new file mode 100644 index 00000000..33b69e4d --- /dev/null +++ b/Components/NotificationProcessor/Capture.php @@ -0,0 +1,92 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * @param Notification $notification + * @throws ORMException + * @throws OptimisticLockException + * @throws TransactionRequiredException + * @throws Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $order = $notification->getOrder(); + + $this->eventManager->notify( + Event::NOTIFICATION_PROCESS_CAPTURE, + [ + 'order' => $order, + 'notification' => $notification + ] + ); + + if ($notification->isSuccess()) { + $this->paymentStatusUpdate->updatePaymentStatus( + $order, + Status::PAYMENT_STATE_THE_CREDIT_HAS_BEEN_ACCEPTED + ); + } + } +} diff --git a/Components/NotificationProcessor/CaptureFailed.php b/Components/NotificationProcessor/CaptureFailed.php new file mode 100644 index 00000000..b8cbc8d2 --- /dev/null +++ b/Components/NotificationProcessor/CaptureFailed.php @@ -0,0 +1,69 @@ +logger = $logger; + $this->eventManager = $eventManager; + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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 new file mode 100644 index 00000000..1646cba9 --- /dev/null +++ b/Components/NotificationProcessor/Chargeback.php @@ -0,0 +1,90 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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 new file mode 100644 index 00000000..f2d1178d --- /dev/null +++ b/Components/NotificationProcessor/ChargebackReversed.php @@ -0,0 +1,90 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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_THE_CREDIT_HAS_BEEN_ACCEPTED); + } +} diff --git a/Components/NotificationProcessor/NotificationProcessorInterface.php b/Components/NotificationProcessor/NotificationProcessorInterface.php new file mode 100644 index 00000000..f3af8574 --- /dev/null +++ b/Components/NotificationProcessor/NotificationProcessorInterface.php @@ -0,0 +1,34 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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 new file mode 100644 index 00000000..1ef470d7 --- /dev/null +++ b/Components/NotificationProcessor/RefundFailed.php @@ -0,0 +1,80 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $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 new file mode 100644 index 00000000..40c03afa --- /dev/null +++ b/Components/NotificationProcessor/RefundedReversed.php @@ -0,0 +1,83 @@ +logger = $logger; + $this->eventManager = $eventManager; + $this->paymentStatusUpdate = $paymentStatusUpdate->setLogger($this->logger); + } + + /** + * Returns boolean on whether this processor can process the Notification object + * + * @param Notification $notification + * @return boolean + */ + public function supports(Notification $notification): bool + { + return strtoupper($notification->getEventCode()) === self::EVENT_CODE; + } + + /** + * Actual processing of the notification + * + * @param Notification $notification + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + * @throws \Enlight_Event_Exception + */ + public function process(Notification $notification) + { + $order = $notification->getOrder(); + + $this->eventManager->notify( + Event::NOTIFICATION_PROCESS_REFUNDED_REVERSED, + [ + 'order' => $order, + 'notification' => $notification + ] + ); + + $this->paymentStatusUpdate->updatePaymentStatus($order, Status::PAYMENT_STATE_THE_CREDIT_HAS_BEEN_ACCEPTED); + } +} diff --git a/Components/OriginKeysService.php b/Components/OriginKeysService.php new file mode 100644 index 00000000..a940b83b --- /dev/null +++ b/Components/OriginKeysService.php @@ -0,0 +1,127 @@ +originKeysService = $originKeysService; + $this->models = $models; + $this->configWriter = $configWriter; + $this->shopwareVersionCheck = $shopwareVersionCheck; + } + + /** + * @param Shop[]|null $shops + * @return array + * @throws AdyenException + */ + public function generate(array $shops = null) + { + if (!$shops) { + $shops = $this->models->getRepository(Shop::class)->findAll(); + } + + $domains = []; + foreach ($shops as $shop) { + $domains[$shop->getId()] = $this->getDomain($shop); + } + + $keys = $this->originKeysService->generate(array_values($domains)); + $shopKeys = []; + foreach ($domains as $shopId => $domain) { + if (!isset($keys[$domain])) { + continue; + } + + $shopKeys[$shopId] = $keys[$domain]; + } + + return $shopKeys; + } + + /** + * @throws AdyenException + */ + public function generateAndSave() + { + $plugin = $this->models->getRepository(Plugin::class)->findOneBy(['name' => AdyenPayment::NAME]); + $shops = $this->models->getRepository(Shop::class)->findAll(); + $keys = $this->generate($shops); + + foreach ($shops as $shop) { + if (!isset($keys[$shop->getId()])) { + continue; + } + $this->configWriter->saveConfigElement( + $plugin, + 'origin_key', + $keys[$shop->getId()], + $shop + ); + } + + if ($this->shopwareVersionCheck->isHigherThanShopwareVersion('v5.5.6')) { + Shopware()->Container()->get('shopware.cache_manager')->clearByTags([CacheManager::CACHE_TAG_CONFIG]); + } + } + + /** + * @param $shop + * @return string + */ + private function getDomain($shop) + { + $hostName = $shop->getHost(); + $isSecure = $shop->getSecure(); + $mainShop = $shop->getMain(); + if ($mainShop) { + $hostName = $mainShop->getHost(); + $isSecure = $mainShop->getSecure(); + } + + return ($isSecure ? 'https://' : 'http://') . $hostName; + } +} diff --git a/Components/Payload/Chain.php b/Components/Payload/Chain.php new file mode 100644 index 00000000..69125cc4 --- /dev/null +++ b/Components/Payload/Chain.php @@ -0,0 +1,40 @@ +providers = $providers; + } + + /** + * @param PaymentContext $context + * @return array + */ + public function provide(PaymentContext $context): array + { + return array_reduce( + $this->providers, + 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 new file mode 100644 index 00000000..00263e2f --- /dev/null +++ b/Components/Payload/PaymentContext.php @@ -0,0 +1,132 @@ +paymentInfo = $paymentInfo; + $this->order = $order; + $this->basket = $basket; + $this->browserInfo = $browserInfo; + $this->shopperInfo = $shopperInfo; + $this->origin = $origin; + $this->transaction = $transaction; + } + + /** + * @return array + */ + public function getPaymentInfo(): array + { + return $this->paymentInfo; + } + + /** + * @return Order + */ + public function getOrder(): Order + { + return $this->order; + } + + /** + * @return sBasket + */ + public function getBasket(): sBasket + { + return $this->basket; + } + + /** + * @return array + */ + public function getBrowserInfo(): array + { + return $this->browserInfo; + } + + /** + * @return array + */ + public function getShopperInfo(): array + { + return $this->shopperInfo; + } + + /** + * @return string + */ + public function getOrigin(): string + { + return $this->origin; + } + + public function getTransaction(): PaymentInfo + { + return $this->transaction; + } +} diff --git a/Components/Payload/PaymentPayloadProvider.php b/Components/Payload/PaymentPayloadProvider.php new file mode 100644 index 00000000..fd7a1a12 --- /dev/null +++ b/Components/Payload/PaymentPayloadProvider.php @@ -0,0 +1,18 @@ +configuration = $configuration; + $this->modelManager = Shopware()->Container()->get('models'); + } + + /** + * @param PaymentContext $context + * @return array + */ + public function provide(PaymentContext $context): array + { + $returnUrl = Shopware()->Router()->assemble([ + 'controller' => 'process', + 'action' => 'return', + ]); + + $plugin = $this->modelManager->getRepository(Plugin::class)->findOneBy(['name' => AdyenPayment::NAME]); + + return [ + 'additionalData' => [ + 'executeThreeD' => true, + 'allow3DS2' => true, + ], + "channel" => "Web", + 'origin' => $context->getOrigin(), + 'returnUrl' => $returnUrl, + 'merchantAccount' => $this->configuration->getMerchantAccount(), + 'applicationInfo' => [ + 'adyenPaymentSource' => [ + 'name' => $plugin->getLabel(), + 'version' => $plugin->getVersion(), + ], + 'externalPlatform' => [ + 'name' => 'Shopware', + 'version' => '5.6', + 'integrator' => $plugin->getAuthor(), + ], + 'merchantApplication' => [ + 'name' => $plugin->getLabel(), + 'version' => $plugin->getVersion(), + ], + ], + ]; + } +} diff --git a/Components/Payload/Providers/BrowserInfoProvider.php b/Components/Payload/Providers/BrowserInfoProvider.php new file mode 100644 index 00000000..2d76877d --- /dev/null +++ b/Components/Payload/Providers/BrowserInfoProvider.php @@ -0,0 +1,28 @@ + $_SERVER['HTTP_ACCEPT'] ?? '', + ]; + + return [ + 'browserInfo' => array_merge($browserInfo, $context->getBrowserInfo()), + ]; + } +} diff --git a/Components/Payload/Providers/LineItemsInfoProvider.php b/Components/Payload/Providers/LineItemsInfoProvider.php new file mode 100644 index 00000000..00b4c6e9 --- /dev/null +++ b/Components/Payload/Providers/LineItemsInfoProvider.php @@ -0,0 +1,115 @@ +priceCalculationService = $priceCalculationService; + $this->adyenCurrency = new Currency(); + } + + /** + * @param PaymentContext $context + * @return array + * @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) + ), + ]; + } + + /** + * @param PaymentContext $context + * @return array + */ + private function buildOrderLines(PaymentContext $context) + { + $orderLines = []; + $currencyCode = $context->getOrder()->getCurrency(); + + /** @var Detail $detail */ + foreach ($context->getOrder()->getDetails() as $detail) { + $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; + } + + /** + * @param PaymentContext $context + * @return array + */ + private function buildShippingLines(PaymentContext $context) + { + $currencyCode = $context->getOrder()->getCurrency(); + $amountExcludingTax = $this->adyenCurrency->sanitize( + $context->getOrder()->getInvoiceShippingNet(), + $currencyCode + ); + $amountIncludingTax = $this->adyenCurrency->sanitize( + $context->getOrder()->getInvoiceShipping(), + $currencyCode + ); + + $shippingLines[] = [ + 'quantity' => 1, + 'amountExcludingTax' => $amountExcludingTax, + 'taxPercentage' => $this->adyenCurrency->sanitize( + $context->getOrder()->getInvoiceShippingTaxRate(), + $currencyCode + ), + 'description' => $context->getOrder()->getDispatch()->getName(), + 'id' => $context->getOrder()->getDispatch()->getId(), + 'taxAmount' => $amountIncludingTax - $amountExcludingTax, + 'amountIncludingTax' => $amountIncludingTax + ]; + + return $shippingLines; + } +} diff --git a/Components/Payload/Providers/OrderInfoProvider.php b/Components/Payload/Providers/OrderInfoProvider.php new file mode 100644 index 00000000..73be8356 --- /dev/null +++ b/Components/Payload/Providers/OrderInfoProvider.php @@ -0,0 +1,32 @@ +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 new file mode 100644 index 00000000..a441c6ad --- /dev/null +++ b/Components/Payload/Providers/PaymentMethodProvider.php @@ -0,0 +1,24 @@ + $context->getPaymentInfo(), + ]; + } +} diff --git a/Components/Payload/Providers/ShopperInfoProvider.php b/Components/Payload/Providers/ShopperInfoProvider.php new file mode 100644 index 00000000..b45785f9 --- /dev/null +++ b/Components/Payload/Providers/ShopperInfoProvider.php @@ -0,0 +1,40 @@ + $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()->getId(), + 'countryCode' => $context->getOrder()->getBilling()->getCountry()->getIso(), + 'billingAddress' => [ + 'city' => $context->getOrder()->getBilling()->getCity(), + 'country' => $context->getOrder()->getBilling()->getCountry()->getIso(), + 'houseNumberOrName' => $context->getOrder()->getBilling()->getNumber(), + 'postalCode' => $context->getOrder()->getBilling()->getZipCode(), + 'street' => $context->getOrder()->getBilling()->getStreet(), + ], + ]; + } +} diff --git a/Components/PaymentMethodService.php b/Components/PaymentMethodService.php new file mode 100644 index 00000000..26b439ea --- /dev/null +++ b/Components/PaymentMethodService.php @@ -0,0 +1,232 @@ +modelManager = $modelManager; + $this->session = $session; + $this->snippetManager = $snippetManager; + $this->adyenPaymentMethodService = $adyenPaymentMethodService; + } + + /** + * @return int + */ + public function getAdyenPaymentId() + { + if ($this->adyenId) { + return (int)$this->adyenId; + } + + $this->adyenId = $this->modelManager->getDBALQueryBuilder() + ->select(['id']) + ->from('s_core_paymentmeans', 'p') + ->where('name = :name') + ->setParameter('name', AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) + ->setMaxResults(1) + ->execute() + ->fetchColumn(); + return (int)$this->adyenId; + } + + /** + * @param bool $prependAdyen + * @return string + */ + public function getActiveUserAdyenMethod($prependAdyen = true) + { + $userId = $this->session->offsetGet('sUserId'); + if (empty($userId)) { + return 'false'; + } + return $this->getUserAdyenMethod((int)$userId, $prependAdyen); + } + + /** + * @param int $userId + * @param bool $prependAdyen + * @return string + */ + public function getUserAdyenMethod(int $userId, $prependAdyen = true) + { + $qb = $this->modelManager->getDBALQueryBuilder(); + $qb->select('a.' . AdyenPayment::ADYEN_PAYMENT_PAYMENT_METHOD) + ->from('s_user_attributes', 'a') + ->where('a.userId = :customerId') + ->setParameter('customerId', $userId); + return ($prependAdyen ? Configuration::PAYMENT_PREFIX : '') . $qb->execute()->fetchColumn(); + } + + /** + * @param int $userId + * @param $payment + * @return bool + */ + public function setUserAdyenMethod(int $userId, $payment) + { + $qb = $this->modelManager->getDBALQueryBuilder(); + $qb->update('s_user_attributes', 'a') + ->set('a.' . AdyenPayment::ADYEN_PAYMENT_PAYMENT_METHOD, ':payment') + ->where('a.userId = :customerId') + ->setParameter('payment', $payment) + ->setParameter('customerId', $userId) + ->execute(); + } + + /** + * @param $payment + * @return bool + */ + public function isAdyenMethod($payment) + { + return substr((string)$payment, 0, Configuration::PAYMENT_LENGHT) === Configuration::PAYMENT_PREFIX; + } + + /** + * @param $payment + * @return false|string + */ + public function getAdyenMethod($payment) + { + return substr((string)$payment, Configuration::PAYMENT_LENGHT); + } + + /** + * @param $type + * @return PaymentMethodInfo + * @throws AdyenException + */ + public function getAdyenPaymentInfoByType($type, $paymentMethods = null) + { + if (!$paymentMethods) { + $paymentMethodOptions = $this->getPaymentMethodOptions(); + $adyenMethods = $this->adyenPaymentMethodService->getPaymentMethods( + $paymentMethodOptions['countryCode'], + $paymentMethodOptions['currency'], + $paymentMethodOptions['value'] + ); + + $paymentMethods = $adyenMethods['paymentMethods']; + } + + foreach ($paymentMethods as $paymentMethod) { + if ($paymentMethod['type'] === $type) { + $name = $this->snippetManager + ->getNamespace('adyen/method/name') + ->get($type, $paymentMethod['name'], true); + + if (empty($name)) { + $name = $paymentMethod['name']; + } + + $description = $this->snippetManager + ->getNamespace('adyen/method/description') + ->get($type); + + $paymentMethodInfo = (new PaymentMethodInfo())->setName($name); + if ($description) { + $paymentMethodInfo->setDescription($description); + } + + return $paymentMethodInfo; + } + } + return new PaymentMethodInfo(); + } + + /** + * @param $adyenMethod + * @return string + */ + public function getAdyenImage($adyenMethod) + { + $type = $adyenMethod['type']; + return $this->getAdyenImageByType($type); + } + + /** + * @param $type + * @return string + */ + public function getAdyenImageByType($type) + { + if ($type === 'scheme') { + $type = 'card'; + } + return sprintf('https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/%s.svg', $type); + } + + public function getPaymentMethodOptions() + { + $countryCode = Shopware()->Session()->sOrderVariables['sUserData']['additional']['country']['countryiso']; + if (!$countryCode) { + $countryCode = Shopware()->Modules()->Admin()->sGetUserData()['additional']['country']['countryiso']; + } + + $currency = Shopware()->Session()->sOrderVariables['sBasket']['sCurrencyName']; + if (!$currency) { + $currency = Shopware()->Shop()->getCurrency()->getCurrency(); + } + + $value = Shopware()->Session()->sOrderVariables['sBasket']['AmountNumeric']; + + $paymentMethodOptions['countryCode'] = $countryCode; + $paymentMethodOptions['currency'] = $currency; + $paymentMethodOptions['value'] = $value ?? 1; + + return $paymentMethodOptions; + } +} diff --git a/Components/PaymentStatusUpdate.php b/Components/PaymentStatusUpdate.php new file mode 100644 index 00000000..b836071d --- /dev/null +++ b/Components/PaymentStatusUpdate.php @@ -0,0 +1,67 @@ +modelManager = $modelManager; + } + + /** + * @param Order $order + * @param int $statusId + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + */ + public function updatePaymentStatus(Order $order, int $statusId) + { + $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() + ]); + } + + $order->setPaymentStatus($paymentStatus); + $this->modelManager->persist($order); + $this->modelManager->flush(); + } + + /** + * @param LoggerInterface $logger + * @return $this + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + return $this; + } +} diff --git a/Components/ShopwareVersionCheck.php b/Components/ShopwareVersionCheck.php new file mode 100644 index 00000000..fc80a0a5 --- /dev/null +++ b/Components/ShopwareVersionCheck.php @@ -0,0 +1,58 @@ +container = $container; + $this->logger = $logger; + } + + public function isHigherThanShopwareVersion(string $shopwareVersion): bool + { + if (!$this->container->has('shopware.release')) { + return true; + } + + $version = $this->container->get('shopware.release')->getVersion(); + + if ($version === self::SHOPWARE) { + try { + list($composerVersion, $sha) = explode('@', Versions::getVersion('shopware/shopware')); + $version = $composerVersion; + } catch (OutOfBoundsException $ex) { + $this->logger->error($ex); + } + } + + if ($version[0] !== 'v') { + $version = 'v' . $version; + } + + return version_compare($shopwareVersion, $version, '<'); + } +} diff --git a/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php b/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php new file mode 100644 index 00000000..08a89ff0 --- /dev/null +++ b/Controllers/Backend/AdyenPaymentNotificationsListingExtension.php @@ -0,0 +1,69 @@ +leftJoin('notification.order', 'nOrder') + ->addSelect(['nOrder']) + ->where("notification.status != :notificationStatus") + ->setParameter('notificationStatus', NotificationStatus::STATUS_HANDLED); + + 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() + { + $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() + { + $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 new file mode 100644 index 00000000..4c5fa6de --- /dev/null +++ b/Controllers/Backend/AdyenPaymentRefund.php @@ -0,0 +1,25 @@ +Request()->getParam('orderId'); + $notificationManager = $this->get('adyen_payment.components.adyen.refund_service'); + $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[] + */ + public function getWhitelistedCSRFActions() + { + return ['refund']; + } +} diff --git a/Controllers/Backend/TestAdyenApi.php b/Controllers/Backend/TestAdyenApi.php new file mode 100644 index 00000000..2ea00ed3 --- /dev/null +++ b/Controllers/Backend/TestAdyenApi.php @@ -0,0 +1,27 @@ +get('adyen_payment.components.adyen.payment.method'); + $configuration = $this->get('adyen_payment.components.configuration'); + + $responseText = 'Adyen API failed, check error logs'; + $this->response->setHttpResponseCode(Response::HTTP_INTERNAL_SERVER_ERROR); + + if (empty($configuration->getApiKey()) || empty($configuration->getMerchantAccount())) { + $this->response->setHttpResponseCode(Response::HTTP_INTERNAL_SERVER_ERROR); + $responseText = 'Missing API configuration. Save the configuration form before testing'; + } + + if (!empty($paymentMethodService->getPaymentMethods('BE', 'EUR', 'nl_NL', 20, false))) { + $this->response->setHttpResponseCode(Response::HTTP_OK); + $responseText = 'Adyen API connected'; + } + + $this->View()->assign('responseText', $responseText); + } +} diff --git a/Controllers/Frontend/Adyen.php b/Controllers/Frontend/Adyen.php new file mode 100644 index 00000000..a0ee2b19 --- /dev/null +++ b/Controllers/Frontend/Adyen.php @@ -0,0 +1,268 @@ +adyenManager = $this->get('adyen_payment.components.manager.adyen_manager'); + $this->adyenCheckout = $this->get('adyen_payment.components.adyen.payment.method'); + $this->basketService = $this->get('adyen_payment.components.basket_service'); + $this->configuration = $this->get('adyen_payment.components.configuration'); + $this->logger = $this->get('adyen_payment.logger'); + $this->priceCalculationService = $this->get('adyen_payment.components.calculator.price_calculation_service'); + } + + public function ajaxDoPaymentAction() + { + $this->Request()->setHeader('Content-Type', 'application/json'); + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + + $context = $this->createPaymentContext(); + + $chain = new Chain( + new ApplicationInfoProvider($this->configuration), + new ShopperInfoProvider(), + new OrderInfoProvider(), + new PaymentMethodProvider(), + new LineItemsInfoProvider($this->priceCalculationService), + new BrowserInfoProvider() + ); + + try { + $payload = $chain->provide($context); + $checkout = $this->adyenCheckout->getCheckout(); + $paymentInfo = $checkout->payments($payload); + + $this->adyenManager->storePaymentDataInSession($paymentInfo['paymentData']); + $this->handlePaymentData($paymentInfo); + $this->Response()->setBody(json_encode( + [ + 'status' => 'success', + 'content' => $paymentInfo + ] + )); + } catch (\Adyen\AdyenException $e) { + $this->logger->debug($e); + $this->Response()->setBody(json_encode( + [ + 'status' => 'error', + 'content' => $e->getMessage() + ] + )); + } + } + + /** + * @throws \Adyen\AdyenException + */ + public function ajaxIdentifyShopperAction() + { + $this->paymentDetails('threeds2_fingerprint', 'threeds2.fingerprint'); + } + + /** + * @throws \Adyen\AdyenException + */ + public function ajaxChallengeShopperAction() + { + $this->paymentDetails('threeds2_challengeResult', 'threeds2.challengeResult'); + } + + public function resetValidPaymentSessionAction() + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->adyenManager->unsetValidPaymentSession(); + } + + /** + * @param $post + * @param $detail + * @throws \Adyen\AdyenException + */ + private function paymentDetails($post, $detail) + { + $this->Request()->setHeader('Content-Type', 'application/json'); + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + + $postData = $this->Request()->getPost($post); + + $payload = [ + 'paymentData' => $this->adyenManager->getPaymentDataSession(), + 'details' => [ + $detail => $postData + ] + ]; + + $checkout = $this->adyenCheckout->getCheckout(); + $paymentInfo = $checkout->paymentsDetails($payload); + $this->handlePaymentData($paymentInfo); + $this->Response()->setBody(json_encode($paymentInfo)); + } + + /** + * @return PaymentContext + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + private function createPaymentContext() + { + $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'); + + return new PaymentContext( + $paymentInfo, + $order, + Shopware()->Modules()->Basket(), + $browserInfo, + $shopperInfo, + $origin, + $transaction + ); + } + + /** + * @return PaymentInfo + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + private function prepareTransaction() + { + $transaction = new PaymentInfo(); + $transaction->setOrderId(-1); + $transaction->setPspReference(''); + + $this->getModelManager()->persist($transaction); + $this->getModelManager()->flush($transaction); + + return $transaction; + } + + /** + * @param $transaction + * @return Order + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + private function prepareOrder($transaction) + { + $signature = $this->persistBasket(); + + $orderNumber = $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 + */ + private function getShopperInfo() + { + 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) + { + if (!in_array( + $paymentInfo['resultCode'], + ['Authorised', 'IdentifyShopper', 'ChallengeShopper', '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($paymentInfo) + { + if ($paymentInfo['merchantReference']) { + $this->basketService->cancelAndRestoreByOrderNumber($paymentInfo['merchantReference']); + } + } +} diff --git a/Controllers/Frontend/Notification.php b/Controllers/Frontend/Notification.php new file mode 100644 index 00000000..ee0f0366 --- /dev/null +++ b/Controllers/Frontend/Notification.php @@ -0,0 +1,164 @@ +checkAuthentication()) { + $this->View()->assign('[Invalid or missing auth]'); + return; + } + + $notifications = $this->getNotificationItems(); + + if (!$this->checkHMAC($notifications)) { + $this->View()->assign('[wrong hmac detected]'); + return; + } + + if (!$this->saveNotifications($notifications)) { + $this->View()->assign('[notification save error]'); + return; + } + + $this->View()->assign('[accepted]'); + } + + /** + * @return mixed + * @throws Enlight_Event_Exception + */ + private function getNotificationItems() + { + $jsonbody = json_decode($this->Request()->getRawBody(), true); + $notificationItems = $jsonbody['notificationItems']; + + $this->events->notify( + Event::NOTIFICATION_RECEIVE, + [ + 'items' => $notificationItems + ] + ); + + return $notificationItems; + } + + /** + * @param $notifications + * @return bool + * @throws AdyenException + */ + private function checkHMAC($notifications) + { + /** @var Configuration $configuration */ + $configuration = $this->get('adyen_payment.components.configuration'); + $adyenUtils = new HmacSignature(); + + foreach ($notifications as $notificationItem) { + $params = $notificationItem['NotificationRequestItem']; + $hmacCheck = $adyenUtils->isValidNotificationHMAC($configuration->getNotificationHmac(), $params); + if (!$hmacCheck) { + $this->get('adyen_payment.logger.notifications')->notice('Invalid HMAC detected'); + return false; + } + } + return true; + } + + /** + * @param array $notifications + * @return Generator + * @throws Enlight_Event_Exception + */ + private function saveNotifications(array $notifications) + { + $notifications = $this->events->filter( + Event::NOTIFICATION_SAVE_FILTER_NOTIFICATIONS, + $notifications + ); + + return iterator_count($this->incomingNotificationsManager->save($notifications)) === 0; + } + + /** + * Whitelist notifyAction + */ + public function getWhitelistedCSRFActions() + { + return ['index']; + } + + /** + * @throws Exception + */ + public function preDispatch() + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + $this->events = $this->get('events'); + $this->incomingNotificationsManager = $this->get('adyen_payment.components.incoming_notification_manager'); + } + + public function postDispatch() + { + $data = $this->View()->getAssign(); + $pretty = $this->Request()->getParam('pretty', false); + + array_walk_recursive($data, static function (&$value) { + // Convert DateTime instances to ISO-8601 Strings + if ($value instanceof DateTime) { + $value = $value->format(DateTime::ISO8601); + } + }); + + $data = Zend_Json::encode($data); + if ($pretty) { + $data = Zend_Json::prettyPrint($data); + } + + $this->Response()->setHeader('content-type', 'application/json', true); + $this->Response()->setBody($data); + } + + private function checkAuthentication() + { + /** @var Configuration $configuration */ + $configuration = $this->get('adyen_payment.components.configuration'); + + if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { + return false; + } + + $authUsername = $_SERVER['PHP_AUTH_USER']; + $authPassword = $_SERVER['PHP_AUTH_PW']; + + if ($authUsername !== $configuration->getNotificationAuthUsername() || + $authPassword !== $configuration->getNotificationAuthPassword()) { + return false; + } + + return true; + } +} diff --git a/Controllers/Frontend/Process.php b/Controllers/Frontend/Process.php new file mode 100644 index 00000000..43c58a65 --- /dev/null +++ b/Controllers/Frontend/Process.php @@ -0,0 +1,158 @@ +adyenManager = $this->get('adyen_payment.components.manager.adyen_manager'); + $this->adyenCheckout = $this->get('adyen_payment.components.adyen.payment.method'); + $this->basketService = $this->get('adyen_payment.components.basket_service'); + $this->logger = $this->get('adyen_payment.logger'); + } + + /** + * @throws Exception + */ + public function returnAction() + { + $this->Front()->Plugins()->ViewRenderer()->setNoRender(); + + $response = $this->Request()->getParams(); + + if ($response) { + $result = $this->validateResponse($response); + $this->handleReturnResult($result); + + switch ($result['resultCode']) { + case PaymentResultCodes::AUTHORISED: + case PaymentResultCodes::PENDING: + case PaymentResultCodes::RECEIVED: + $this->redirect([ + 'controller' => 'checkout', + 'action' => 'finish', + 'sAGB' => true + ]); + break; + case PaymentResultCodes::CANCELLED: + case PaymentResultCodes::ERROR: + case PaymentResultCodes::REFUSED: + default: + if (!empty($result['merchantReference'])) { + $this->basketService->cancelAndRestoreByOrderNumber($result['merchantReference']); + } + $this->redirect([ + 'controller' => 'checkout', + 'action' => 'confirm' + ]); + break; + + } + } + } + + /** + * @param $result + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\TransactionRequiredException + */ + private function handleReturnResult($result) + { + $orderNumber = $result['merchantReference']; + /** @var Order $order */ + $order = $this->getModelManager()->getRepository(Order::class)->findOneBy([ + 'number' => $orderNumber + ]); + + if (!$order) { + $this->logger->error('No order found for ', [ + 'ordernumber' => $orderNumber, + ]); + + return; + } + + switch ($result['resultCode']) { + case 'Authorised': + case 'Pending': + case 'Received': + $paymentStatus = $this->getModelManager()->find(Status::class, + Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED); + break; + case 'Cancelled': + case 'Error': + case 'Fail': + case '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; + } + + $order->setPaymentStatus($paymentStatus); + $order->setTransactionId($result['pspReference']); + $this->getModelManager()->persist($order); + } + + /** + * Validates the payload from checkout /payments hpp and returns the api response + * + * @param $response + * @return mixed + */ + private function validateResponse($response) + { + $request['paymentData'] = $this->adyenManager->getPaymentDataSession(); + $request['details'] = $response; + + try { + $checkout = $this->adyenCheckout->getCheckout(); + $response = $checkout->paymentsDetails($request); + } catch (\Adyen\AdyenException $e) { + $response['error'] = $e->getMessage(); + } + + return $response; + } +} diff --git a/Exceptions/InvalidParameterException.php b/Exceptions/InvalidParameterException.php new file mode 100644 index 00000000..3937ca6a --- /dev/null +++ b/Exceptions/InvalidParameterException.php @@ -0,0 +1,15 @@ +getConstants()); + } +} diff --git a/Models/Enum/PaymentResultCodes.php b/Models/Enum/PaymentResultCodes.php new file mode 100644 index 00000000..54920c9f --- /dev/null +++ b/Models/Enum/PaymentResultCodes.php @@ -0,0 +1,18 @@ +message = $message; + $this->notificationItem = $notificationItem; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @param string $message + * @return NotificationItemFeedback + */ + public function setMessage(string $message): NotificationItemFeedback + { + $this->message = $message; + return $this; + } + + /** + * @return array + */ + public function getNotificationItem(): array + { + return $this->notificationItem; + } + + /** + * @param array $notificationItem + * @return NotificationItemFeedback + */ + public function setNotificationItem(array $notificationItem): NotificationItemFeedback + { + $this->notificationItem = $notificationItem; + return $this; + } +} diff --git a/Models/Feedback/NotificationProcessorFeedback.php b/Models/Feedback/NotificationProcessorFeedback.php new file mode 100644 index 00000000..9cbbeacb --- /dev/null +++ b/Models/Feedback/NotificationProcessorFeedback.php @@ -0,0 +1,97 @@ +success = $success; + $this->message = $message; + $this->notification = $notification; + } + + /** + * @return bool + */ + public function isSuccess(): bool + { + return $this->success; + } + + /** + * @param bool $success + * @return NotificationProcessorFeedback + */ + public function setSuccess(bool $success): NotificationProcessorFeedback + { + $this->success = $success; + return $this; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @param string $message + * @return NotificationProcessorFeedback + */ + public function setMessage(string $message): NotificationItemFeedback + { + $this->message = $message; + return $this; + } + + /** + * @return Notification + */ + public function getNotification(): Notification + { + return $this->notification; + } + + /** + * @param Notification $notification + * @return NotificationProcessorFeedback + */ + public function setNotification(Notification $notification): NotificationProcessorFeedback + { + $this->notification = $notification; + return $this; + } +} diff --git a/Models/Notification.php b/Models/Notification.php new file mode 100644 index 00000000..042555bc --- /dev/null +++ b/Models/Notification.php @@ -0,0 +1,389 @@ +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 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 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 new file mode 100644 index 00000000..582ef559 --- /dev/null +++ b/Models/NotificationException.php @@ -0,0 +1,39 @@ +notification = $notification; + + parent::__construct($message, $code, $previous); + } + + /** + * @return Notification + */ + public function getNotification(): Notification + { + return $this->notification; + } +} diff --git a/Models/PaymentInfo.php b/Models/PaymentInfo.php new file mode 100644 index 00000000..f1b133c7 --- /dev/null +++ b/Models/PaymentInfo.php @@ -0,0 +1,198 @@ +setCreatedAt(new \DateTime('now')); + $this->setUpdatedAt(new \DateTime('now')); + } + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @param int $id + * @return $this + */ + public function setId(int $id) + { + $this->id = $id; + return $this; + } + + /** + * @return int + */ + public function getOrderId(): int + { + return $this->orderId; + } + + /** + * @param int $orderId + * @return $this + */ + public function setOrderId(int $orderId) + { + $this->orderId = $orderId; + return $this; + } + + + /** + * @return Order|null + */ + public function getOrder() + { + return $this->order; + } + + /** + * @param Order|null $order + * @return $this + */ + public function setOrder(Order $order = null) + { + $this->order = $order; + return $this; + } + + + /** + * @return string + */ + public function getPspReference(): string + { + return $this->pspReference; + } + + + /** + * @param string $pspReference + * @return $this + */ + public function setPspReference(string $pspReference) + { + $this->pspReference = $pspReference; + return $this; + } + + /** + * @return \DateTime + */ + public function getCreatedAt(): \DateTime + { + return $this->createdAt; + } + + /** + * @param \DateTime $createdAt + * @return $this + */ + public function setCreatedAt(\DateTime $createdAt) + { + $this->createdAt = $createdAt; + return $this; + } + + /** + * @return \DateTime + */ + public function getUpdatedAt(): \DateTime + { + return $this->updatedAt; + } + + /** + * @param \DateTime $updatedAt + * @return $this + */ + public function setUpdatedAt(\DateTime $updatedAt) + { + $this->updatedAt = $updatedAt; + return $this; + } + + /** + * @return string + */ + public function getResultCode(): string + { + return $this->resultCode; + } + + /** + * @param string $resultCode + * @return $this + */ + public function setResultCode(string $resultCode) + { + $this->resultCode = $resultCode; + return $this; + } +} diff --git a/Models/PaymentMethodInfo.php b/Models/PaymentMethodInfo.php new file mode 100644 index 00000000..f0025a07 --- /dev/null +++ b/Models/PaymentMethodInfo.php @@ -0,0 +1,65 @@ +name = ''; + $this->description = ''; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + * @return PaymentMethodInfo + */ + public function setName(string $name): PaymentMethodInfo + { + $this->name = $name; + return $this; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + * @return PaymentMethodInfo + */ + public function setDescription(string $description): PaymentMethodInfo + { + $this->description = $description; + return $this; + } +} diff --git a/Models/Refund.php b/Models/Refund.php new file mode 100644 index 00000000..157fc019 --- /dev/null +++ b/Models/Refund.php @@ -0,0 +1,162 @@ +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/ShopwareInfo.php b/Models/ShopwareInfo.php new file mode 100644 index 00000000..32f743af --- /dev/null +++ b/Models/ShopwareInfo.php @@ -0,0 +1,52 @@ +=7.0 +* Shopware >=5.6 + +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). + +## Installation +Please use the [official documentation]() of the plugin to see how to install it. + +## Usage +Please use the [official documentation]() of the plugin to see how to configure and use it. + +## Documentation +Please find the relevant documentation for + - [How to start with Adyen]() + - [Shopware 5 official plugin]() + - [Adyen PHP API Library](https://docs.adyen.com/development-resources/libraries#php) + +## Support +If you have a feature request, or spotted a bug or a technical problem, create a GitHub issue. For other questions, +contact our [support team](https://support.adyen.com/hc/en-us/requests/new?ticket_form_id=360000705420). + +# For developers + +## 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). \ No newline at end of file diff --git a/Resources/config.xml b/Resources/config.xml new file mode 100755 index 00000000..5f000431 --- /dev/null +++ b/Resources/config.xml @@ -0,0 +1,108 @@ + + + + + origin_key + + + true + + + + environment + + + + + + + + merchant_account + + + + api_key + + + + api_url_prefix + + + + notification_hmac + + + + notification_auth_username + + + + notification_auth_password + + + + js_components_3DS2_challenge_image_size + + + + + + + + + + + google_merchant_id + + + + testAPIconnection + + + + + + + + + diff --git a/Resources/cronjob.xml b/Resources/cronjob.xml new file mode 100644 index 00000000..7a0486cc --- /dev/null +++ b/Resources/cronjob.xml @@ -0,0 +1,11 @@ + + + + Adyen: Process Notifications + Shopware_CronJob_AdyenPaymentProcessNotifications + true + 60 + false + + \ No newline at end of file diff --git a/Resources/frontend/js/jquery.adyen-checkout-error.js b/Resources/frontend/js/jquery.adyen-checkout-error.js new file mode 100644 index 00000000..286f98f5 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-checkout-error.js @@ -0,0 +1,114 @@ +;(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 new file mode 100644 index 00000000..ba118610 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-confirm-order.js @@ -0,0 +1,319 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-confirm-order', { + /** + * Plugin default options. + */ + defaults: { + placeOrderSelector: '.table--actions button[type=submit]', + confirmFormSelector: '#confirm--form', + mountRedirectSelector: '.is--act-confirm', + adyenType: '', + adyen3ds2challengeimagesize: '', + adyenGoogleConfig: {}, + adyenSetSession: {}, + adyenAjaxDoPaymentUrl: '/frontend/adyen/ajaxDoPayment', + adyenAjaxIdentifyShopperUrl: '/frontend/adyen/ajaxIdentifyShopper', + adyenAjaxChallengeShopperUrl: '/frontend/adyen/ajaxChallengeShopper', + adyenSnippets: { + errorTransactionCancelled: 'Your transaction was cancelled by the Payment Service Provider.', + errorTransactionProcessing: 'An error occured 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.', + }, + }, + paymentMethodSession: 'paymentMethod', + adyenConfiguration: {}, + adyenCheckout: null, + + init: function () { + var me = this; + + me.sessionStorage = StorageManager.getStorage('session'); + + 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 (!$.isEmptyObject(me.opts.adyenSetSession)) { + me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(me.opts.adyenSetSession)); + } + }, + + onPlaceOrder: function (event) { + var me = this; + + if (typeof event !== 'undefined') { + event.preventDefault(); + } + + me.clearAdyenError(); + + if (me.sessionStorage.getItem(me.paymentMethodSession)) { + if (!$(me.opts.confirmFormSelector)[0].checkValidity()) { + return; + } + + $.loadingIndicator.open(); + + var data = { + 'paymentMethod': me.getPaymentMethod(), + 'browserInfo': me.getBrowserInfo(), + 'origin': window.location.origin + }; + + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.adyenAjaxDoPaymentUrl, + data: data, + success: function (response) { + if (response['status'] === 'success') { + me.handlePaymentData(response['content']); + } else { + me.addAdyenError(response['content']); + } + + $.loadingIndicator.close(); + }, + }); + } else { + if ($('body').data('adyenisadyenpayment')) { + this.addAdyenError(me.opts.adyenSnippets.errorTransactionNoSession); + return; + } + + $(me.opts.confirmFormSelector).submit(); + } + }, + + handlePaymentData: function (data) { + var me = this; + + switch (data.resultCode) { + case 'Authorised': + me.handlePaymentDataAuthorised(data); + break; + case 'IdentifyShopper': + me.handlePaymentDataIdentifyShopper(data); + break; + case 'ChallengeShopper': + me.handlePaymentDataChallengeShopper(data); + break; + case 'RedirectShopper': + me.handlePaymentDataRedirectShopper(data); + break; + default: + me.handlePaymentDataError(data); + break; + } + }, + + handlePaymentDataAuthorised: function (data) { + var me = this; + $(me.opts.confirmFormSelector).submit(); + }, + + handlePaymentDataIdentifyShopper: function (data) { + var me = this; + + $(me.opts.placeOrderSelector).parent().append('
'); + me.adyenCheckout + .create('threeDS2DeviceFingerprint', { + fingerprintToken: data.authentication['threeds2.fingerprintToken'], + onComplete: function (fingerprintData) { + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.adyenAjaxIdentifyShopperUrl, + data: fingerprintData.data.details, + success: function (response) { + me.handlePaymentData(response); + }, + }); + }, + onError: function (error) { + console.error(error); + } + }) + .mount('#AdyenIdentifyShopperThreeDS2'); + }, + + handlePaymentDataChallengeShopper: function (data) { + var me = this; + + var modal = $.modal.open('
', { + showCloseButton: false, + closeOnOverlay: false, + additionalClass: 'adyen-challenge-shopper' + }); + me.adyenCheckout + .create('threeDS2Challenge', { + challengeToken: data.authentication['threeds2.challengeToken'], + onComplete: function (challengeData) { + modal.close(); + $.ajax({ + method: 'POST', + dataType: 'json', + url: me.opts.adyenAjaxChallengeShopperUrl, + data: challengeData.data.details, + success: function (response) { + me.handlePaymentData(response); + }, + }); + }, + onError: function (error) { + console.log(error); + }, + size: me.getThreeDS2ChallengeSize(), + }) + .mount('#AdyenChallengeShopperThreeDS2'); + }, + + handlePaymentDataRedirectShopper: function (data) { + var me = this; + if (data.action.type === 'redirect') { + me.adyenCheckout.createFromAction(data.action).mount(me.opts.mountRedirectSelector); + } + }, + + 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; + + if (me.opts.adyenType === 'paywithgoogle') { + me.replaceCheckoutButtonForGooglePay(); + } + }, + + replaceCheckoutButtonForGooglePay: function () { + var me = this; + + var orderButton = $(me.opts.placeOrderSelector); + orderButton.parent().append( + $('
') + .attr('id', 'AdyenGooglePayButton') + .addClass('right') + ); + orderButton.remove(); + + me.opts.adyenGoogleConfig.onSubmit = function (state, component) { + me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(state.data.paymentMethod)); + me.onPlaceOrder(); + }; + + var googlepay = me.adyenCheckout.create("paywithgoogle", me.opts.adyenGoogleConfig); + googlepay + .isAvailable() + .then(function () { + googlepay.mount("#AdyenGooglePayButton"); + }) + .catch(function (e) { + this.addAdyenError(me.opts.adyenSnippets.errorGooglePayNotAvailable); + }); + }, + getThreeDS2ChallengeSize: function () { + var me = this; + + return '0' + me.opts.adyen3ds2challengeimagesize; + }, + 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 adyenConfig = me.getAdyenConfigSession(); + + me.adyenConfiguration = { + locale: adyenConfig.locale, + environment: adyenConfig.environment, + originKey: adyenConfig.originKey, + paymentMethodsResponse: adyenConfig.paymentMethodsResponse, + onAdditionalDetails: $.proxy(me.handleOnAdditionalDetails, me), + }; + }, + + setCheckout: function () { + var me = this; + + me.adyenCheckout = new AdyenCheckout(me.adyenConfiguration); + }, + + getPaymentMethod: function () { + var me = this; + + return me.sessionStorage.getItem(me.paymentMethodSession); + }, + + 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); \ No newline at end of file diff --git a/Resources/frontend/js/jquery.adyen-finish-order.js b/Resources/frontend/js/jquery.adyen-finish-order.js new file mode 100644 index 00000000..f7f9ba37 --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-finish-order.js @@ -0,0 +1,23 @@ +;(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); \ No newline at end of file diff --git a/Resources/frontend/js/jquery.adyen-payment-selection.js b/Resources/frontend/js/jquery.adyen-payment-selection.js new file mode 100644 index 00000000..de12fa1b --- /dev/null +++ b/Resources/frontend/js/jquery.adyen-payment-selection.js @@ -0,0 +1,299 @@ +;(function ($) { + 'use strict'; + + $.plugin('adyen-payment-selection', { + /** + * Plugin default options. + */ + defaults: { + adyenOriginkey: '', + adyenPaymentMethodsResponse: {}, + resetSessionUrl: '', + /** + * Fallback environment variable + * + * @type {string} + */ + adyenEnvironment: 'test', + /** + * Default shopLocale when no locate is assigned + * + * @type {string} + */ + shopLocale: 'en-US', + /** + * Prefix to identify adyen payment methods + * + * @type {String} + */ + adyenPaymentMethodPrefix: 'adyen_', + /** + * Selector for the payment form. + * + * @type {String} + */ + formSelector: '#shippingPaymentForm', + /** + * Selector for the payment method select fields. + * + * @type {String} + */ + paymentMethodSelector: '.payment--method', + /** + * Selector for the payment method label wrapper. + * + * @type {String} + */ + methodLabelSelector: '.method--label', + /** + * 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: 'button[type=submit]', + }, + + currentSelectedPaymentId: '', + currentSelectedPaymentType: '', + adyenConfiguration: {}, + adyenCheckout: null, + changeInfosButton: null, + paymentMethodSession: 'paymentMethod', + adyenConfigSession: 'adyenConfig', + + init: function () { + var me = this; + + me.sessionStorage = StorageManager.getStorage('session'); + + me.applyDataAttributes(); + me.eventListeners(); + me.setConfig(); + me.setCheckout(); + me.handleSelectedMethod(); + }, + eventListeners: function () { + var me = this; + + $(document).on('submit', me.opts.formSelector, $.proxy(me.onPaymentFormSubmit, me)); + $.subscribe(me.getEventName('plugin/swShippingPayment/onInputChangedBefore'), $.proxy(me.onPaymentChangedBefore, me)); + $.subscribe(me.getEventName('plugin/swShippingPayment/onInputChanged'), $.proxy(me.onPaymentChangedAfter, me)); + }, + onPaymentFormSubmit: function (e) { + var me = this; + if ($(me.opts.paymentMethodFormSubmitSelector).hasClass('is--disabled')) { + e.preventDefault(); + return false; + } + }, + onPaymentChangedBefore: function ($event) { + var me = this; + + me.currentSelectedPaymentId = event.target.id; + me.currentSelectedPaymentType = $(event.target).val(); + }, + onPaymentChangedAfter: function () { + var me = this; + var payment; + + //Return when no adyen payment + if (me.currentSelectedPaymentType.indexOf(me.opts.adyenPaymentMethodPrefix) === -1) { + me.sessionStorage.removeItem(me.paymentMethodSession); + return; + } + + payment = me.getPaymentMethodByType(me.currentSelectedPaymentType); + + //When details is set load the component + if (typeof payment.details !== "undefined") { + $('#' + me.currentSelectedPaymentId) + .closest(me.opts.paymentMethodSelector) + .find(me.opts.methodBankdataSelector) + .prop('id', me.getCurrentComponentId(me.currentSelectedPaymentId)); + + $(me.opts.paymentMethodFormSubmitSelector).addClass('is--disabled'); + me.handleComponent(payment.type); + + return; + } + + me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(payment)); + }, + setConfig: function () { + var me = this; + + me.adyenConfiguration = { + locale: me.opts.shopLocale, + environment: me.opts.adyenEnvironment, + originKey: me.opts.adyenOriginkey, + paymentMethodsResponse: me.opts.adyenPaymentMethodsResponse, + onChange: $.proxy(me.handleOnChange, me), + }; + + me.saveAdyenConfigInSession(me.adyenConfiguration); + }, + getCurrentComponentId: function (currentSelectedPaymentId) { + return 'component-' + currentSelectedPaymentId; + }, + getPaymentMethodByType(type) { + var me = this; + + type = type.split(me.opts.adyenPaymentMethodPrefix).pop(); + return me.opts.adyenPaymentMethodsResponse['paymentMethods'].find(function (paymentMethod) { + return paymentMethod.type === type + }); + }, + setCheckout: function () { + var me = this; + + me.adyenCheckout = new AdyenCheckout(me.adyenConfiguration); + }, + handleComponent: function (type) { + var me = this; + + switch (type) { + case 'paywithgoogle': + me.handleComponentPayWithGoogle(type); + break; + default: + me.handleComponentGeneral(type); + break; + } + }, + handleComponentGeneral: function (type) { + var me = this; + me.adyenCheckout.create(type).mount('#' + me.getCurrentComponentId(me.currentSelectedPaymentId)); + }, + handleComponentPayWithGoogle: function (type) { + 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; + + var form = $(me.opts.formSelector); + var paymentMethod = form.find('input[name=payment]:checked'); + var paymentMethodContainer = form.find('input[name=payment]:checked').closest(me.opts.paymentMethodSelector); + + if (!me.isPaymentMethodValid(paymentMethod)) { + return; + } + + //Return when redirect + var payment = me.getPaymentMethodByType(paymentMethod.val()); + if (typeof payment.details === "undefined") { + return; + } + + me.currentSelectedPaymentId = paymentMethod.attr('id'); + me.currentSelectedPaymentType = paymentMethod.val(); + + // Return when no data has been entered yet + see if component is needed + if (!me.sessionStorage.getItem(me.paymentMethodSession) || + me.sessionStorage.getItem(me.paymentMethodSession) === "{}") { + me.onPaymentChangedAfter(); + return; + } + + me.changeInfosButton = $('') + .addClass(me.opts.classChangePaymentInfo) + .html('Update your payment information') + .on('click', $.proxy(me.updatePaymentInfo, me)); + paymentMethodContainer.find(me.opts.methodLabelSelector).append(me.changeInfosButton); + }, + isPaymentMethodValid: function (paymentMethod) { + var me = this; + + if (!paymentMethod.length) { + return false; + } + + //Return when no adyen payment + if (paymentMethod.val().indexOf(me.opts.adyenPaymentMethodPrefix) === -1) { + return false; + } + + //Return when redirect + if (typeof me.getPaymentMethodByType(paymentMethod.val()).details === "undefined") { + return false; + } + + return true; + }, + 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.getPaymentMethodByType(paymentMethod.val()); + + //When details is set load the component + if (typeof payment.details !== "undefined") { + $('#' + me.currentSelectedPaymentId) + .closest(me.opts.paymentMethodSelector) + .find(me.opts.methodBankdataSelector) + .prop('id', me.getCurrentComponentId(me.currentSelectedPaymentId)); + + me.handleComponent(payment.type); + + if (me.changeInfosButton) { + me.changeInfosButton.remove(); + me.changeInfosButton = null; + } + } + }, + setPaymentSession: function (state) { + var me = this; + me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(state.data.paymentMethod)); + }, + removePaymentSession: function () { + var me = this; + + me.sessionStorage.removeItem(me.paymentMethodSession); + $.get(me.opts.resetSessionUrl); + }, + saveAdyenConfigInSession: function (adyenConfiguration) { + var me = this; + + var data = { + locale: adyenConfiguration.locale, + environment: adyenConfiguration.environment, + originKey: adyenConfiguration.originKey, + paymentMethodsResponse: adyenConfiguration.paymentMethodsResponse + }; + + me.sessionStorage.setItem(me.adyenConfigSession, JSON.stringify(data)); + }, + }); + +})(jQuery); \ No newline at end of file diff --git a/Resources/frontend/js/jquery.plugin-loader.js b/Resources/frontend/js/jquery.plugin-loader.js new file mode 100644 index 00000000..f3ddd1d8 --- /dev/null +++ b/Resources/frontend/js/jquery.plugin-loader.js @@ -0,0 +1,10 @@ +;(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'); + }); +})(jQuery); \ No newline at end of file diff --git a/Resources/frontend/less/all.less b/Resources/frontend/less/all.less new file mode 100644 index 00000000..415ba20f --- /dev/null +++ b/Resources/frontend/less/all.less @@ -0,0 +1,34 @@ +.payment--method { + .method--image { + width: 60px; + text-align: center; + float: left; + display: inline-flex; + align-items: center; + justify-content: center; + + img { + width: 50px; + } + } + .method--change-info { + float: right; + cursor: pointer; + } +} + +.payment--content { + .payment--method-image { + img { + width: 50px; + } + } +} + +.adyen-challenge-shopper .content { + padding: 20px; +} + +.alert.is--adyen-error { + margin-bottom: 12px; +} \ No newline at end of file diff --git a/Resources/menu.xml b/Resources/menu.xml new file mode 100644 index 00000000..91403fae --- /dev/null +++ b/Resources/menu.xml @@ -0,0 +1,14 @@ + + + + + AdyenPaymentNotificationsListingExtension + + AdyenPaymentNotificationsListingExtension + index + sprite-mail-share + ConfigurationMenu + + + diff --git a/Resources/services.xml b/Resources/services.xml new file mode 100644 index 00000000..a43c0745 --- /dev/null +++ b/Resources/services.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/Resources/services/commands.xml b/Resources/services/commands.xml new file mode 100644 index 00000000..e2904c41 --- /dev/null +++ b/Resources/services/commands.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/Resources/services/components.xml b/Resources/services/components.xml new file mode 100644 index 00000000..eb3857a5 --- /dev/null +++ b/Resources/services/components.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/services/cronjobs.xml b/Resources/services/cronjobs.xml new file mode 100644 index 00000000..001d04c2 --- /dev/null +++ b/Resources/services/cronjobs.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/Resources/services/loggers.xml b/Resources/services/loggers.xml new file mode 100644 index 00000000..1e1d2210 --- /dev/null +++ b/Resources/services/loggers.xml @@ -0,0 +1,40 @@ + + + + + + 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 new file mode 100644 index 00000000..2cd31061 --- /dev/null +++ b/Resources/services/managers.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/services/subscribers.xml b/Resources/services/subscribers.xml new file mode 100644 index 00000000..46a75234 --- /dev/null +++ b/Resources/services/subscribers.xml @@ -0,0 +1,64 @@ + + + + + + %adyen_payment.plugin_dir% + + + + + + + + + + + + + + + + + %adyen_payment.plugin_dir% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/services/version/563.xml b/Resources/services/version/563.xml new file mode 100644 index 00000000..34bb8df7 --- /dev/null +++ b/Resources/services/version/563.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js b/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js new file mode 100755 index 00000000..76020f0a --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/app.js @@ -0,0 +1,29 @@ + +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 new file mode 100755 index 00000000..1c160ee8 --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/controller/main.js @@ -0,0 +1,11 @@ + +// {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 new file mode 100755 index 00000000..099c5742 --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js @@ -0,0 +1,41 @@ + +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 new file mode 100755 index 00000000..86123682 --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/store/notification.js @@ -0,0 +1,11 @@ + +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 new file mode 100644 index 00000000..054ea8ea --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/extensions/notification_filter.js @@ -0,0 +1,59 @@ +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 new file mode 100755 index 00000000..ce16a999 --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/notification.js @@ -0,0 +1,93 @@ + +// {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, + columns: { + 'pspReference': { }, + 'createdAt': { }, + 'updatedAt': { }, + 'status': { }, + 'paymentMethod': { }, + 'eventCode': { }, + 'success': { }, + 'merchantAccountCode': { }, + 'amountValue': { }, + 'amountCurrency': { }, + 'errorDetails': { }, + 'orderId': { + renderer: this.orderIdRenderer + }, + } + }; + }, + + orderIdRenderer: function (value, styles, row) { + return 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}', + }, + + createActionColumn: function () { + var me = this; + + return Ext.create('Ext.grid.column.Action', { + width: 30, + items:[ + me.createEditOrderColumn(), + ] + }); + }, + + createEditOrderColumn: function () { + var me = this; + + return { + iconCls: 'sprite-eye', + action: 'editOrder', + tooltip: me.snippets.columns.orderDetails, + + handler: function (view, rowIndex, colIndex, item) { + var store = view.getStore(), + record = store.getAt(rowIndex); + + 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 new file mode 100755 index 00000000..5ed6d709 --- /dev/null +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/view/list/window.js @@ -0,0 +1,18 @@ + +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 new file mode 100644 index 00000000..1636470e --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/app.js @@ -0,0 +1,10 @@ +//{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 new file mode 100644 index 00000000..f9bc4aa2 --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/model/order.js @@ -0,0 +1,6 @@ +// + +//{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 new file mode 100644 index 00000000..e40da291 --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications.js @@ -0,0 +1,53 @@ +// + +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 new file mode 100644 index 00000000..4ddc6bae --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/detail.js @@ -0,0 +1,41 @@ +// + +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 new file mode 100644 index 00000000..15cc6429 --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/tabs/notifications/list.js @@ -0,0 +1,41 @@ +// + +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 new file mode 100644 index 00000000..ae55c6d9 --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds.js @@ -0,0 +1,29 @@ +// + +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 new file mode 100644 index 00000000..508f986d --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/tabs/refunds/detail.js @@ -0,0 +1,96 @@ +// + +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 new file mode 100644 index 00000000..724a545c --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/transaction_details.js @@ -0,0 +1,119 @@ +// + +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: 'Transaction details', + 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'}, + ]; + }, + +}); \ No newline at end of file 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 new file mode 100644 index 00000000..e994de6f --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/transaction_tabs.js @@ -0,0 +1,50 @@ +// + +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 new file mode 100644 index 00000000..8c70da38 --- /dev/null +++ b/Resources/views/backend/adyen_payment_order/view/detail/window.js @@ -0,0 +1,79 @@ +//{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(); + + if (!me.record.raw.adyenTransaction) { + return result; + } + + result.add(me.createAdyenTab()); + + return result; + }, + + /** + * Generate Adyen Tab + */ + createAdyenTab: function () { + var me = this; + + var transactionStore = Ext.create('Shopware.apps.AdyenPaymentNotificationsListingExtension.store.Notification'); + + me.transactionDetails = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionDetails', { + store: transactionStore, + layout: { + type: 'vbox', + align: 'stretch' + }, + region: 'north' + }); + + me.transactionTabsDetail = Ext.create('Shopware.apps.AdyenPaymentOrder.view.detail.TransactionTabs', { + region: 'center', + store: transactionStore, + record: me.record + }); + + me.adyenTransactionTab = Ext.create('Ext.container.Container', { + title: 'Adyen Transactions', + layout: 'border', + items: [ + me.transactionDetails, + me.transactionTabsDetail + ] + }); + + 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} \ No newline at end of file diff --git a/Resources/views/backend/customer/adyen_payment_method/app.js b/Resources/views/backend/customer/adyen_payment_method/app.js new file mode 100644 index 00000000..8595d5c9 --- /dev/null +++ b/Resources/views/backend/customer/adyen_payment_method/app.js @@ -0,0 +1,4 @@ +//{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 new file mode 100644 index 00000000..4d7a8bef --- /dev/null +++ b/Resources/views/backend/customer/adyen_payment_method/view/list.js @@ -0,0 +1,22 @@ +// +// {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/order/adyen_payment_method/app.js b/Resources/views/backend/order/adyen_payment_method/app.js new file mode 100644 index 00000000..25493ea5 --- /dev/null +++ b/Resources/views/backend/order/adyen_payment_method/app.js @@ -0,0 +1,4 @@ +//{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 new file mode 100644 index 00000000..9afd333d --- /dev/null +++ b/Resources/views/backend/order/adyen_payment_method/view/list.js @@ -0,0 +1,22 @@ +// +// {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/checkout/adyen_libaries.tpl b/Resources/views/frontend/checkout/adyen_libaries.tpl new file mode 100644 index 00000000..fc47184b --- /dev/null +++ b/Resources/views/frontend/checkout/adyen_libaries.tpl @@ -0,0 +1,13 @@ +{block name='frontend_checkout_payment_content_adyen_libaries'} + {if $sAdyenGoogleConfig} + + {/if} + + + +{/block} \ No newline at end of file diff --git a/Resources/views/frontend/checkout/change_payment.tpl b/Resources/views/frontend/checkout/change_payment.tpl new file mode 100644 index 00000000..2bc399eb --- /dev/null +++ b/Resources/views/frontend/checkout/change_payment.tpl @@ -0,0 +1,26 @@ +{extends file="parent:frontend/checkout/change_payment.tpl"} + +{block name='frontend_checkout_payment_content'} + {include file="frontend/checkout/adyen_libaries.tpl"} + + {if $sAdyenConfig} +
+
+ {/if} + + {$smarty.block.parent} +{/block} + +{block name='frontend_checkout_payment_fieldset_input_label'} + {if $payment_mean.image} +
+ {$payment_mean.description} +
+ {/if} + {$smarty.block.parent} +{/block} \ No newline at end of file diff --git a/Resources/views/frontend/checkout/confirm.tpl b/Resources/views/frontend/checkout/confirm.tpl new file mode 100644 index 00000000..b1e2d9f1 --- /dev/null +++ b/Resources/views/frontend/checkout/confirm.tpl @@ -0,0 +1,49 @@ +{extends file="parent:frontend/checkout/confirm.tpl"} + +{block name='frontend_checkout_confirm_form'} + {include file="frontend/checkout/adyen_libaries.tpl"} + + {$smarty.block.parent} +{/block} + +{block name="frontend_checkout_confirm_left_payment_method"} + {if $sUserData.additional.payment.image} +
+ {$sUserData.additional.payment.description} +
+ {/if} + + {$smarty.block.parent} +{/block} + +{block name='frontend_index_body_attributes'} + {$smarty.block.parent} + {if $mAdyenSnippets}data-AdyenSnippets="{$mAdyenSnippets}"{/if} + data-adyenAjaxDoPaymentUrl="{url module='frontend' controller='adyen' action='ajaxDoPayment'}" + data-adyenAjaxIdentifyShopperUrl="{url module='frontend' controller='adyen' action='ajaxIdentifyShopper'}" + data-adyenAjaxChallengeShopperUrl="{url module='frontend' controller='adyen' action='ajaxChallengeShopper'}" + {if $mAdyenSnippets} + data-adyenSnippets="{$mAdyenSnippets}" + {/if} + {if $sUserData.additional.payment.type} + data-adyenType="{$sUserData.additional.payment.type}" + {/if} + {if $sAdyenGoogleConfig} + data-adyenGoogleConfig='{$sAdyenGoogleConfig}' + {/if} + {if $sAdyenConfig} + data-adyen3DS2ChallengeImageSize='{$sAdyenConfig.jsComponents3DS2ChallengeImageSize}' + {/if} + {if $sAdyenSetSession} + data-adyenSetSession='{$sAdyenSetSession}' + {/if} + {if $sUserData.additional.payment.name == 'adyen_general_payment_method'} + data-adyenIsAdyenPayment='true' + {/if} +{/block} + +{block name='frontend_checkout_confirm_error_messages'} +
+ + {$smarty.block.parent} +{/block} \ No newline at end of file diff --git a/Subscriber/AccountPaymentSubscriber.php b/Subscriber/AccountPaymentSubscriber.php new file mode 100644 index 00000000..ee1c74d0 --- /dev/null +++ b/Subscriber/AccountPaymentSubscriber.php @@ -0,0 +1,99 @@ +shopwarePaymentMethodService = $shopwarePaymentMethodService; + $this->session = $session; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PreDispatch_Frontend_Account' => 'beforeAccount', + 'Enlight_Controller_Action_PostDispatch_Frontend_Account' => 'afterAccount' + ]; + } + + /** + * @param \Enlight_Event_EventArgs $args + */ + public function beforeAccount(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Frontend_Account $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['savePayment'])) { + return; + } + + $payment = $subject->Request()->getPost('register')['payment']; + if (!$payment || !is_string($payment)) { + return; + } + + if ($this->shopwarePaymentMethodService->isAdyenMethod($payment)) { + $paymentId = $this->shopwarePaymentMethodService->getAdyenPaymentId(); + + $subject->Request()->setPost('register', ['payment' => $paymentId]); + $subject->Request()->setPost( + 'adyenPayment', $this->shopwarePaymentMethodService->getAdyenMethod($payment) + ); + } + } + + /** + * @param \Enlight_Event_EventArgs $args + */ + public function afterAccount(\Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Account $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['payment'])) { + return; + } + + $formData = $subject->View()->getAssign('sFormData'); + if (!$formData['payment']) { + return; + } + if ((int)$formData['payment'] !== $this->shopwarePaymentMethodService->getAdyenPaymentId()) { + return; + } + $formData['payment'] = $this->shopwarePaymentMethodService->getActiveUserAdyenMethod(); + $subject->View()->assign('sFormData', $formData); + } +} diff --git a/Subscriber/BackendConfigSubscriber.php b/Subscriber/BackendConfigSubscriber.php new file mode 100644 index 00000000..a302eaf3 --- /dev/null +++ b/Subscriber/BackendConfigSubscriber.php @@ -0,0 +1,77 @@ +originKeysService = $originKeysService; + $this->logger = $logger; + $this->shopwareVersionCheck = $shopwareVersionCheck; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PostDispatch_Backend_Config' => 'onBackendConfig' + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + * @throws \Adyen\AdyenException + */ + public function onBackendConfig(\Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Backend_Config $subject */ + $subject = $args->getSubject(); + + if ($subject->Request()->getActionName() === 'saveForm' && $subject->Request()->getParam('name') === AdyenPayment::NAME) { + try { + $this->originKeysService->generateAndSave(); + } catch (AdyenException $e) { + $this->logger->error($e); + } + + if ($this->shopwareVersionCheck->isHigherThanShopwareVersion('v5.5.6')) { + $subject->get('shopware.cache_manager')->clearByTags([CacheManager::CACHE_TAG_CONFIG]); + } + } + } +} diff --git a/Subscriber/BackendJavascriptSubscriber.php b/Subscriber/BackendJavascriptSubscriber.php new file mode 100644 index 00000000..22c6d13e --- /dev/null +++ b/Subscriber/BackendJavascriptSubscriber.php @@ -0,0 +1,110 @@ +pluginDirectory = $pluginDirectory; + $this->notificationRepository = $modelManager->getRepository(Notification::class); + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PostDispatchSecure_Backend_Order' => 'onOrderPostDispatch', + 'Enlight_Controller_Action_PostDispatchSecure_Backend_Customer' => 'onCustomerPostDispatch' + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + */ + public function onOrderPostDispatch(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Backend_Customer $controller */ + $controller = $args->getSubject(); + + $view = $controller->View(); + $request = $controller->Request(); + + $view->addTemplateDir($this->pluginDirectory . '/Resources/views'); + + if ($request->getActionName() === 'index') { + $view->extendsTemplate('backend/order/adyen_payment_method/app.js'); + } + + if ($request->getActionName() === 'getList') { + $this->onGetList($args); + } + } + + public function onCustomerPostDispatch(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Backend_Customer $controller */ + $controller = $args->getSubject(); + + $view = $controller->View(); + $request = $controller->Request(); + + $view->addTemplateDir($this->pluginDirectory . '/Resources/views'); + + if ($request->getActionName() === 'index') { + $view->extendsTemplate('backend/customer/adyen_payment_method/app.js'); + } + + if ($request->getActionName() === 'getOrders') { + $this->onGetList($args); + } + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function onGetList(Enlight_Event_EventArgs $args) + { + $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/BackendOrderSubscriber.php b/Subscriber/BackendOrderSubscriber.php new file mode 100644 index 00000000..a822c873 --- /dev/null +++ b/Subscriber/BackendOrderSubscriber.php @@ -0,0 +1,110 @@ +modelManager = $modelManager; + $this->paymentInfoRepository = $this->modelManager->getRepository(PaymentInfo::class); + $this->notificationManager = $notificationManager; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PostDispatchSecure_Backend_Order' => 'onBackendOrder', + 'Shopware_Modules_Order_SendMail_Send' => 'onSendMail' + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + */ + public function onBackendOrder(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Backend_Order $subject */ + $subject = $args->getSubject(); + + if ($subject->Request()->getActionName() !== 'getList') { + return; + } + + $data = $subject->View()->getAssign('data'); + + $this->addTransactionData($data); + + $subject->View()->assign('data', $data); + } + + /** + * @param array $data + * @throws \Doctrine\ORM\NonUniqueResultException + */ + private function addTransactionData(array &$data) + { + foreach ($data as &$order) { + $order['adyenTransaction'] = null; + $order['adyenNotification'] = null; + $order['adyenRefundable'] = false; + + if ($order['payment']['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) { + 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', + ]); + } + } + } +} diff --git a/Subscriber/BackendPaymentSubscriber.php b/Subscriber/BackendPaymentSubscriber.php new file mode 100644 index 00000000..a70924c6 --- /dev/null +++ b/Subscriber/BackendPaymentSubscriber.php @@ -0,0 +1,53 @@ + 'afterBackendPayment' + ]; + } + + /** + * @param \Enlight_Event_EventArgs $args + */ + public function afterBackendPayment(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Backend_Payment $subject */ + $subject = $args->getSubject(); + + if ($subject->Request()->getActionName() === 'getPayments') { + $this->afterGetPayments($args); + } + } + + /** + * @param \Enlight_Event_EventArgs $args + */ + private function afterGetPayments(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Backend_Payment $subject */ + $subject = $args->getSubject(); + + $data = $subject->View()->getAssign('data'); + + $data = array_values(array_filter($data, function ($e) { + return $e['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD; + })); + + $subject->View()->assign('data', $data); + } +} diff --git a/Subscriber/CheckoutSubscriber.php b/Subscriber/CheckoutSubscriber.php new file mode 100644 index 00000000..d633a0b8 --- /dev/null +++ b/Subscriber/CheckoutSubscriber.php @@ -0,0 +1,459 @@ +configuration = $configuration; + $this->paymentMethodService = $paymentMethodService; + $this->shopwarePaymentMethodService = $shopwarePaymentMethodService; + $this->session = $session; + $this->modelManager = $modelManager; + $this->snippets = $snippets; + $this->front = $front; + $this->adyenManager = $adyenManager; + $this->dataConversion = $dataConversion; + $this->originKeysService = $originKeysService; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PreDispatch_Frontend_Checkout' => 'CheckoutFrontendPreDispatch', + 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'CheckoutFrontendPostDispatch', + 'sAdmin::sUpdatePayment::after' => 'sAdminAfterSUpdatePayment', + 'sAdmin::sGetDispatchBasket::after' => 'sAdminAfterSGetDispatchBasket', + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + */ + public function CheckoutFrontendPreDispatch(Enlight_Event_EventArgs $args) + { + $this->rewritePostPayment($args); + $this->unsetPaymentSessions($args); + } + + /** + * @param Enlight_Event_EventArgs $args + * @throws AdyenException + */ + public function CheckoutFrontendPostDispatch(Enlight_Event_EventArgs $args) + { + $this->checkFirstCheckoutStep($args); + $this->rewritePaymentData($args); + $this->addAdyenConfigOnShipping($args); + $this->addAdyenConfigOnConfirm($args); + $this->addAdyenSnippets($args); + $this->addAdyenGooglePay($args); + } + + /** + * @param \Enlight_Hook_HookArgs $args + * @return void + */ + public function sAdminAfterSUpdatePayment(\Enlight_Hook_HookArgs $args) + { + $paymentId = $args->get('paymentId'); + if (!$paymentId) { + $paymentId = $this->front->Request()->getPost('sPayment'); + } + + if ($paymentId !== $this->shopwarePaymentMethodService->getAdyenPaymentId()) { + return; + } + + $userId = (int)$this->session->offsetGet('sUserId'); + if (empty($userId)) { + return; + } + + $adyenPayment = Shopware()->Front()->Request()->getParams()['adyenPayment']; + if (!$adyenPayment) { + return; + } + + $this->shopwarePaymentMethodService->setUserAdyenMethod($userId, $adyenPayment); + } + + /** + * @param \Enlight_Hook_HookArgs $args + * @return mixed + */ + public function sAdminAfterSGetDispatchBasket(\Enlight_Hook_HookArgs $args) + { + $basket = $args->getReturn(); + + if ($this->shopwarePaymentMethodService->isAdyenMethod($basket['paymentID'])) { + $basket['paymentID'] = $this->shopwarePaymentMethodService->getAdyenPaymentId(); + } + + return $basket; + } + + /** + * @param Enlight_Event_EventArgs $args + * @throws AdyenException + */ + private function addAdyenConfigOnShipping(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['shippingPayment'])) { + return; + } + $view = $subject->View(); + + $countryCode = $view->getAssign('sUserData')['additional']['country']['countryiso']; + $currency = $view->getAssign('sBasket')['sCurrencyName']; + $value = $view->getAssign('sBasket')['AmountNumeric']; + $paymentMethods = $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value); + $shop = Shopware()->Shop(); + + $adyenConfig = [ + "shopLocale" => $this->dataConversion->getISO3166FromLocale($shop->getLocale()->getLocale()), + "originKey" => $this->getOriginKey($shop), + "environment" => $this->configuration->getEnvironment($shop), + "paymentMethods" => json_encode($paymentMethods), + "paymentMethodPrefix" => $this->configuration->getPaymentMethodPrefix($shop), + "jsComponents3DS2ChallengeImageSize" => $this->configuration->getJsComponents3DS2ChallengeImageSize($shop), + ]; + + $view->assign('sAdyenConfig', $adyenConfig); + } + + + /** + * @param Enlight_Event_EventArgs $args + */ + private function addAdyenConfigOnConfirm(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['confirm'])) { + return; + } + + $adyenConfig = [ + "jsComponents3DS2ChallengeImageSize" => $this->configuration->getJsComponents3DS2ChallengeImageSize(), + ]; + + $subject->View()->assign('sAdyenConfig', $adyenConfig); + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function addAdyenSnippets(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['confirm'])) { + 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['errorTransactionNoSession'] = $errorSnippets->get( + 'errorTransactionNoSession', + 'Your transaction was cancelled due to an unknown reason. Please make sure your browser allows cookies.', + true + ); + + $subject->View()->assign('mAdyenSnippets', htmlentities(json_encode($snippets))); + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function rewritePaymentData(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['shippingPayment', 'saveShippingPayment'])) { + return; + } + + $formData = $subject->View()->getAssign('sFormData'); + if (!$formData['payment']) { + return; + } + if ((int)$formData['payment'] !== $this->shopwarePaymentMethodService->getAdyenPaymentId()) { + return; + } + $formData['payment'] = $this->shopwarePaymentMethodService->getActiveUserAdyenMethod(); + $subject->View()->assign('sFormData', $formData); + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function rewritePostPayment(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['shippingPayment', 'saveShippingPayment'])) { + return; + } + + $payment = $subject->Request()->get('payment'); + if (!$payment || !is_string($payment)) { + return; + } + + $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_PAYMENT_VALID, false); + if ($this->shopwarePaymentMethodService->isAdyenMethod($payment)) { + $paymentId = $this->shopwarePaymentMethodService->getAdyenPaymentId(); + $adyenPayment = $this->shopwarePaymentMethodService->getAdyenMethod($payment); + + $subject->Request()->setParams([ + 'payment' => $paymentId, + 'adyenPayment' => $adyenPayment + ]); + $subject->Request()->setPost('payment', $paymentId); + $subject->Request()->setPost('adyenPayment', $adyenPayment); + $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_PAYMENT, $adyenPayment); + $this->session->offsetSet(AdyenPayment::SESSION_ADYEN_PAYMENT_VALID, true); + } + } + + private function addAdyenGooglePay(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['confirm'])) { + return; + } + + $userData = $subject->View()->getAssign('sUserData'); + if (!$userData['additional'] || + !$userData['additional']['payment'] || + $userData['additional']['payment']['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) { + return; + } + + $basket = $subject->View()->getAssign('sBasket'); + if (!$basket) { + return; + } + + if ($this->shopwarePaymentMethodService->getActiveUserAdyenMethod(false) !== 'paywithgoogle') { + return; + } + + $currencyUtil = new Currency(); + $adyenGoogleConfig = [ + 'environment' => 'TEST', + 'showPayButton' => true, + 'currencyCode' => $basket['sCurrencyName'], + 'amount' => $currencyUtil->sanitize($basket['AmountNumeric'], $basket['sCurrencyName']), + 'configuration' => [ + 'gatewayMerchantId' => $this->configuration->getMerchantAccount(), + 'merchantName' => Shopware()->Shop()->getName() + ], + ]; + if ($this->configuration->getEnvironment() === Configuration::ENV_LIVE) { + $adyenGoogleConfig['environment'] = 'PRODUCTION'; + $adyenGoogleConfig['configuration']['merchantIdentifier'] = $this->configuration->getGoogleMerchantId(); + } + $subject->View()->assign('sAdyenGoogleConfig', htmlentities(json_encode($adyenGoogleConfig))); + } + + private function checkFirstCheckoutStep(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['confirm'])) { + return; + } + + if ($this->shouldRedirectToStep2($subject)) { + $subject->forward( + 'shippingPayment', + 'checkout' + ); + } + } + + /** + * @param Shopware_Controllers_Frontend_Checkout $subject + * @return bool + * @throws AdyenException + */ + private function shouldRedirectToStep2(Shopware_Controllers_Frontend_Checkout $subject): bool + { + $userData = $subject->View()->getAssign('sUserData'); + if (!$userData['additional'] || + !$userData['additional']['payment'] || + $userData['additional']['payment']['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) { + return false; + } + + $countryCode = Shopware()->Session()->sOrderVariables['sUserData']['additional']['country']['countryiso']; + $currency = Shopware()->Session()->sOrderVariables['sBasket']['sCurrencyName']; + $value = Shopware()->Session()->sOrderVariables['sBasket']['AmountNumeric']; + + $adyenMethods = $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value); + $selectedType = $userData['additional']['user'][AdyenPayment::ADYEN_PAYMENT_PAYMENT_METHOD]; + $adyenMethods['paymentMethods'] = array_filter($adyenMethods['paymentMethods'], + function ($element) use ($selectedType) { + return ($element['type'] === $selectedType); + }); + + if (!count($adyenMethods['paymentMethods'])) { + return true; + } + + if (!array_key_exists('details', reset($adyenMethods['paymentMethods']))) { + $subject->View()->assign('sAdyenSetSession', json_encode(reset($adyenMethods['paymentMethods']))); + return false; + } + + return !$this->session->offsetExists(AdyenPayment::SESSION_ADYEN_PAYMENT_VALID); + } + + private function unsetPaymentSessions(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if ($subject->Request()->getActionName() !== 'finish') { + return; + } + + $this->adyenManager->unsetPaymentDataInSession(); + } + + private function getOriginKey($shop): string + { + if (!$this->configuration->getOriginKey($shop)) { + $this->originKeysService->generateAndSave(); + } + + return $this->configuration->getOriginKey($shop); + } +} diff --git a/Subscriber/CookieSubscriber.php b/Subscriber/CookieSubscriber.php new file mode 100644 index 00000000..75af61a5 --- /dev/null +++ b/Subscriber/CookieSubscriber.php @@ -0,0 +1,32 @@ + 'addAdyenCookie' + ]; + } + + public function addAdyenCookie(): CookieCollection + { + $collection = new CookieCollection(); + + $collection->add(new CookieStruct( + 'comfort', + '/^adyen/', + 'Adyen Cookies', + CookieGroupStruct::TECHNICAL + )); + + return $collection; + } +} diff --git a/Subscriber/Cronjob/ProcessNotifications.php b/Subscriber/Cronjob/ProcessNotifications.php new file mode 100644 index 00000000..d76adb69 --- /dev/null +++ b/Subscriber/Cronjob/ProcessNotifications.php @@ -0,0 +1,74 @@ +loader = $fifoNotificationLoader; + $this->notificationProcessor = $notificationProcessor; + $this->logger = $logger; + } + + public static function getSubscribedEvents() + { + return [ + 'Shopware_CronJob_AdyenPaymentProcessNotifications' => 'runCronjob' + ]; + } + + /** + * @param Shopware_Components_Cron_CronJob $job + * @throws \Doctrine\ORM\ORMException + * @throws \Enlight_Event_Exception + */ + public function runCronjob(Shopware_Components_Cron_CronJob $job) + { + /** @var \Generator $feedback */ + $feedback = $this->notificationProcessor->processMany( + $this->loader->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/FrontendPaymentNameSubscriber.php b/Subscriber/FrontendPaymentNameSubscriber.php new file mode 100644 index 00000000..db15a9c5 --- /dev/null +++ b/Subscriber/FrontendPaymentNameSubscriber.php @@ -0,0 +1,136 @@ +shopwarePaymentMethodService = $shopwarePaymentMethodService; + $this->logger = $logger; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'CheckoutFrontendPostDispatch', + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + */ + public function CheckoutFrontendPostDispatch(Enlight_Event_EventArgs $args) + { + $this->rewriteConfirmPaymentInfo($args); + $this->rewriteFinishPaymentInfo($args); + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function rewriteConfirmPaymentInfo(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['confirm'])) { + return; + } + + $userData = $subject->View()->getAssign('sUserData'); + if (!$userData['additional'] || + !$userData['additional']['payment'] || + $userData['additional']['payment']['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) { + return; + } + + $adyenType = $this->shopwarePaymentMethodService->getActiveUserAdyenMethod(false); + $adyenMethodName = $this->getSelectedAdyenMethodName($adyenType); + if (!$adyenMethodName || empty($adyenMethodName->getName())) { + return; + } + + $userData['additional']['payment']['description'] = $adyenMethodName->getName(); + $userData['additional']['payment']['additionaldescription'] = $adyenMethodName->getDescription(); + $userData['additional']['payment']['image'] = $this->shopwarePaymentMethodService->getAdyenImageByType($adyenType); + $userData['additional']['payment']['type'] = $adyenType; + + $subject->View()->assign('sUserData', $userData); + } + + /** + * @param Enlight_Event_EventArgs $args + */ + private function rewriteFinishPaymentInfo(Enlight_Event_EventArgs $args) + { + /** @var Shopware_Controllers_Frontend_Checkout $subject */ + $subject = $args->getSubject(); + + if (!in_array($subject->Request()->getActionName(), ['finish'])) { + return; + } + + $sPayment = $subject->View()->getAssign('sPayment'); + if ($sPayment['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD) { + return; + } + + $adyenType = $this->shopwarePaymentMethodService->getActiveUserAdyenMethod(false); + $adyenMethodName = $this->getSelectedAdyenMethodName($adyenType); + if (!$adyenMethodName || empty($adyenMethodName->getName())) { + return; + } + + $sPayment['description'] = $adyenMethodName->getName(); + $sPayment['additionaldescription'] = $adyenMethodName->getDescription(); + $sPayment['image'] = $this->shopwarePaymentMethodService->getAdyenImageByType($adyenType); + $subject->View()->assign('sPayment', $sPayment); + } + + /** + * @param $adyenType + * @return \AdyenPayment\Models\PaymentMethodInfo|null + */ + private function getSelectedAdyenMethodName($adyenType) + { + try { + return $this->shopwarePaymentMethodService->getAdyenPaymentInfoByType($adyenType); + } catch (AdyenException $ex) { + $this->logger->notice('Fail loading Adyen description', ['ex' => $ex]); + return null; + } + } +} diff --git a/Subscriber/Notification/LogIncomingNotification.php b/Subscriber/Notification/LogIncomingNotification.php new file mode 100644 index 00000000..dadd9047 --- /dev/null +++ b/Subscriber/Notification/LogIncomingNotification.php @@ -0,0 +1,52 @@ +logger = $logger; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + Event::NOTIFICATION_RECEIVE => 'logNotifications' + ]; + } + + /** + * @param Enlight_Event_EventArgs $args + */ + public function logNotifications(Enlight_Event_EventArgs $args) + { + $items = $args->get('items'); + + foreach ($items as $item) { + $this->logger->debug('Incoming notification', ['json' => $item]); + } + } +} diff --git a/Subscriber/PaymentSubscriber.php b/Subscriber/PaymentSubscriber.php new file mode 100755 index 00000000..53925d2c --- /dev/null +++ b/Subscriber/PaymentSubscriber.php @@ -0,0 +1,117 @@ +paymentMethodService = $paymentMethodService; + $this->shopwarePaymentMethodService = $shopwarePaymentMethodService; + } + + public static function getSubscribedEvents() + { + return [ + 'Shopware_Modules_Admin_GetPaymentMeans_DataFilter' => 'replaceAdyenMethods', + 'Shopware_Controllers_Frontend_Checkout::getSelectedPayment::before' => 'beforeGetSelectedPayment', + ]; + } + + /** + * Skip replacement of payment methods during checkPaymentAvailability validation + */ + public function beforeGetSelectedPayment() + { + $this->skipReplaceAdyenMethods = true; + } + + /** + * Replace general Adyen payment method with dynamic loaded payment methods + * + * @param Enlight_Event_EventArgs $args + * @return array + * @throws AdyenException + */ + public function replaceAdyenMethods(Enlight_Event_EventArgs $args): array + { + $shopwareMethods = $args->getReturn(); + + if ($this->skipReplaceAdyenMethods || !in_array( + Shopware()->Front()->Request()->getActionName(), + ['shippingPayment', 'payment'] + )) { + $this->skipReplaceAdyenMethods = false; + return $shopwareMethods; + } + + $shopwareMethods = array_filter($shopwareMethods, function ($method) { + return $method['name'] !== AdyenPayment::ADYEN_GENERAL_PAYMENT_METHOD; + }); + + $paymentMethodOptions = $this->shopwarePaymentMethodService->getPaymentMethodOptions(); + + $adyenMethods = $this->paymentMethodService->getPaymentMethods( + $paymentMethodOptions['countryCode'], + $paymentMethodOptions['currency'], + $paymentMethodOptions['value'] + ); + + if (empty($adyenMethods)) { + return $shopwareMethods; + } + + $adyenMethods['paymentMethods'] = array_reverse($adyenMethods['paymentMethods']); + + foreach ($adyenMethods['paymentMethods'] as $adyenMethod) { + $paymentMethodInfo = $this->shopwarePaymentMethodService->getAdyenPaymentInfoByType( + $adyenMethod['type'], + $adyenMethods['paymentMethods'] + ); + array_unshift($shopwareMethods, [ + 'id' => Configuration::PAYMENT_PREFIX . $adyenMethod['type'], + 'name' => $adyenMethod['type'], + 'description' => $paymentMethodInfo->getName(), + 'additionaldescription' => $paymentMethodInfo->getDescription(), + 'image' => $this->shopwarePaymentMethodService->getAdyenImage($adyenMethod), + ]); + } + + return $shopwareMethods; + } +} diff --git a/Subscriber/Template.php b/Subscriber/Template.php new file mode 100644 index 00000000..a6f40702 --- /dev/null +++ b/Subscriber/Template.php @@ -0,0 +1,69 @@ +templateManager = $templateManager; + $this->pluginDirectory = $pluginDirectory; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + 'Enlight_Controller_Action_PreDispatch' => 'onPreDispatch', + 'Enlight_Controller_Action_PostDispatchSecure_Backend_Order' => 'onBackendOrderPostDispatch', + ]; + } + + public function onPreDispatch() + { + $this->templateManager->addTemplateDir($this->pluginDirectory . '/Resources/views'); + } + + public function onBackendOrderPostDispatch(\Enlight_Event_EventArgs $args) + { + /** @var \Shopware_Controllers_Backend_Order $controller */ + $controller = $args->getSubject(); + + $view = $controller->View(); + $request = $controller->Request(); + + if ($request->getActionName() === 'index') { + $view->extendsTemplate('backend/adyen_payment_order/app.js'); + } + + if ($request->getActionName() === 'load') { + $view->extendsTemplate('backend/adyen_payment_order/view/detail/window.js'); + $view->extendsTemplate('backend/adyen_payment_order/model/order.js'); + } + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..c3f43925 --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "adyen/adyen-shopware5", + "description": "Adyen plugin Shopware 5", + "type": "shopware-plugin", + "keywords": ["adyen", "payment", "payment platform"], + "homepage": "https://adyen.com", + "license": "MIT", + "authors": [ + { + "name": "Adyen", + "email": "shopware@adyen.com", + "homepage": "https://adyen.com", + "role": "Developer" + } + ], + "require": { + "php": "^7.0", + "ext-json": "*", + "adyen/php-api-library": "^6.1" + }, + "require-dev": { + "shopware/shopware": "^5.6", + "phpro/grumphp": "^0.16.1", + "squizlabs/php_codesniffer": "2.*", + "phpcompatibility/php-compatibility": "^9.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "friendsofphp/php-cs-fixer": "^2.16" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "format": "vendor/bin/php-cs-fixer fix", + "test-php-compatibility" : "vendor/bin/phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 7.0- --extensions=php --ignore=*/vendor/*" + } +} diff --git a/plugin.png b/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..ea0aa365978af394a2e3ac307fe86e6ee14dce29 GIT binary patch literal 592 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D%zBQrxHN+NuH ztdjF{^%7I^lT!66atnZ}7#M6Stboki)RIJnirk#MVyg;UC9n!BAR8pCucQE0Qj%?} z6yY17;GAESs$i;TqGzCF$EBd4U{jQmW)z9|8>y;bpKhp88yV>WRp=I1=9MH?=;jqGLkxkLMfuL^+7WFhI$72aI=A0Z9t+{{zaLoK$}74+Zoz`RicPN?Xl4Z zS&rlwh)=>@JPUP4@D)KXV`NSl-i@wz5t2U(%MS;H`HwUT*x) z+8`)!WSv<=KuZrmH_yBpdR7_ug2Z$WYfK(82%5s*LeRs$4VIe9;6@ O+ + + + + + + 1.4.1 + Adyen + Adyen + https://adyen.com + MIT + + Official Adyen plugin + Offizielles Adyen plugin + + + + + + Release 🎉 + ]]> + + + \ No newline at end of file diff --git a/ruleset.xml b/ruleset.xml new file mode 100644 index 00000000..73d6ef40 --- /dev/null +++ b/ruleset.xml @@ -0,0 +1,6 @@ + + + PSR-2 standards + + + \ No newline at end of file From 1dc0e3dcab94530322d992361a839dcc906c3f53 Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Tue, 16 Jun 2020 11:42:40 +0200 Subject: [PATCH 2/9] phpcs.xml and some minor fixes --- Controllers/Frontend/Process.php | 12 ++++++---- Subscriber/AccountPaymentSubscriber.php | 3 ++- Subscriber/CheckoutSubscriber.php | 6 +++-- phpcs.xml | 32 +++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 phpcs.xml diff --git a/Controllers/Frontend/Process.php b/Controllers/Frontend/Process.php index 43c58a65..7fe92e44 100644 --- a/Controllers/Frontend/Process.php +++ b/Controllers/Frontend/Process.php @@ -115,15 +115,19 @@ private function handleReturnResult($result) case 'Authorised': case 'Pending': case 'Received': - $paymentStatus = $this->getModelManager()->find(Status::class, - Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED); + $paymentStatus = $this->getModelManager()->find( + Status::class, + Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED + ); break; case 'Cancelled': case 'Error': case 'Fail': case 'Refused': - $paymentStatus = $this->getModelManager()->find(Status::class, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED); + $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); diff --git a/Subscriber/AccountPaymentSubscriber.php b/Subscriber/AccountPaymentSubscriber.php index ee1c74d0..0baa9953 100644 --- a/Subscriber/AccountPaymentSubscriber.php +++ b/Subscriber/AccountPaymentSubscriber.php @@ -69,7 +69,8 @@ public function beforeAccount(\Enlight_Event_EventArgs $args) $subject->Request()->setPost('register', ['payment' => $paymentId]); $subject->Request()->setPost( - 'adyenPayment', $this->shopwarePaymentMethodService->getAdyenMethod($payment) + 'adyenPayment', + $this->shopwarePaymentMethodService->getAdyenMethod($payment) ); } } diff --git a/Subscriber/CheckoutSubscriber.php b/Subscriber/CheckoutSubscriber.php index d633a0b8..913363c1 100644 --- a/Subscriber/CheckoutSubscriber.php +++ b/Subscriber/CheckoutSubscriber.php @@ -419,10 +419,12 @@ private function shouldRedirectToStep2(Shopware_Controllers_Frontend_Checkout $s $adyenMethods = $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value); $selectedType = $userData['additional']['user'][AdyenPayment::ADYEN_PAYMENT_PAYMENT_METHOD]; - $adyenMethods['paymentMethods'] = array_filter($adyenMethods['paymentMethods'], + $adyenMethods['paymentMethods'] = array_filter( + $adyenMethods['paymentMethods'], function ($element) use ($selectedType) { return ($element['type'] === $selectedType); - }); + } + ); if (!count($adyenMethods['paymentMethods'])) { return true; diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..1114cfa9 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,32 @@ + + + + + vendor/* + . + + *.js + + From 7a9ccee094e203ce9374db59fc1eedeacd36c57c Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Tue, 16 Jun 2020 11:51:13 +0200 Subject: [PATCH 3/9] PHPCBF fixes --- Controllers/Frontend/Process.php | 13 +++++--- .../frontend/js/jquery.adyen-confirm-order.js | 16 +++++----- .../frontend/js/jquery.adyen-finish-order.js | 2 +- .../model/notification.js | 2 +- .../customer/adyen_payment_method/app.js | 4 +-- .../adyen_payment_method/view/list.js | 2 +- .../backend/order/adyen_payment_method/app.js | 4 +-- .../order/adyen_payment_method/view/list.js | 2 +- Subscriber/AccountPaymentSubscriber.php | 3 +- Subscriber/CheckoutSubscriber.php | 6 ++-- phpcs.xml | 32 +++++++++++++++++++ 11 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 phpcs.xml diff --git a/Controllers/Frontend/Process.php b/Controllers/Frontend/Process.php index 43c58a65..54d8fe6e 100644 --- a/Controllers/Frontend/Process.php +++ b/Controllers/Frontend/Process.php @@ -84,7 +84,6 @@ public function returnAction() 'action' => 'confirm' ]); break; - } } } @@ -115,15 +114,19 @@ private function handleReturnResult($result) case 'Authorised': case 'Pending': case 'Received': - $paymentStatus = $this->getModelManager()->find(Status::class, - Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED); + $paymentStatus = $this->getModelManager()->find( + Status::class, + Status::PAYMENT_STATE_THE_PAYMENT_HAS_BEEN_ORDERED + ); break; case 'Cancelled': case 'Error': case 'Fail': case 'Refused': - $paymentStatus = $this->getModelManager()->find(Status::class, - Status::PAYMENT_STATE_THE_PROCESS_HAS_BEEN_CANCELLED); + $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); diff --git a/Resources/frontend/js/jquery.adyen-confirm-order.js b/Resources/frontend/js/jquery.adyen-confirm-order.js index ba118610..935c7b99 100644 --- a/Resources/frontend/js/jquery.adyen-confirm-order.js +++ b/Resources/frontend/js/jquery.adyen-confirm-order.js @@ -48,7 +48,7 @@ me._on(me.opts.placeOrderSelector, 'click', $.proxy(me.onPlaceOrder, me)); }, - checkSetSession: function() { + checkSetSession: function () { var me = this; if (!$.isEmptyObject(me.opts.adyenSetSession)) { me.sessionStorage.setItem(me.paymentMethodSession, JSON.stringify(me.opts.adyenSetSession)); @@ -147,9 +147,9 @@ }, }); }, - onError: function (error) { - console.error(error); - } + onError: function (error) { + console.error(error); + } }) .mount('#AdyenIdentifyShopperThreeDS2'); }, @@ -177,10 +177,10 @@ }, }); }, - onError: function (error) { - console.log(error); - }, - size: me.getThreeDS2ChallengeSize(), + onError: function (error) { + console.log(error); + }, + size: me.getThreeDS2ChallengeSize(), }) .mount('#AdyenChallengeShopperThreeDS2'); }, diff --git a/Resources/frontend/js/jquery.adyen-finish-order.js b/Resources/frontend/js/jquery.adyen-finish-order.js index f7f9ba37..cf0919c0 100644 --- a/Resources/frontend/js/jquery.adyen-finish-order.js +++ b/Resources/frontend/js/jquery.adyen-finish-order.js @@ -13,7 +13,7 @@ me.cleanupSessions(); }, - cleanupSessions: function() { + cleanupSessions: function () { var me = this; me.sessions.forEach(function (session) { me.sessionStorage.removeItem(session); 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 index 099c5742..efe2d4df 100755 --- a/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js +++ b/Resources/views/backend/adyen_payment_notifications_listing_extension/model/notification.js @@ -18,7 +18,7 @@ Ext.define('Shopware.apps.AdyenPaymentNotificationsListingExtension.model.Notifi { name : 'eventCode', type: 'string' }, { name : 'success', type: 'string' }, { name : 'merchantAccountCode', type: 'string' }, - { name : 'amountValue', type: 'float', convert: function(v) { + { name : 'amountValue', type: 'float', convert: function (v) { return v / 100; } }, { name : 'amountCurrency', type: 'string' }, diff --git a/Resources/views/backend/customer/adyen_payment_method/app.js b/Resources/views/backend/customer/adyen_payment_method/app.js index 8595d5c9..b27ff47d 100644 --- a/Resources/views/backend/customer/adyen_payment_method/app.js +++ b/Resources/views/backend/customer/adyen_payment_method/app.js @@ -1,4 +1,4 @@ //{block name="backend/customer/application"} - //{$smarty.block.parent} - //{include file="backend/customer/adyen_payment_method/view/list.js"} + //{$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 index 4d7a8bef..954ad8e6 100644 --- a/Resources/views/backend/customer/adyen_payment_method/view/list.js +++ b/Resources/views/backend/customer/adyen_payment_method/view/list.js @@ -12,7 +12,7 @@ dataIndex: 'adyen_payment_order_payment', flex:1, sortable: false, - renderer: function(value, metaData, record) { + renderer: function (value, metaData, record) { return record.raw.adyen_payment_order_payment; } }); diff --git a/Resources/views/backend/order/adyen_payment_method/app.js b/Resources/views/backend/order/adyen_payment_method/app.js index 25493ea5..f85d467d 100644 --- a/Resources/views/backend/order/adyen_payment_method/app.js +++ b/Resources/views/backend/order/adyen_payment_method/app.js @@ -1,4 +1,4 @@ //{block name="backend/order/application"} - //{$smarty.block.parent} - //{include file="backend/order/adyen_payment_method/view/list.js"} + //{$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 index 9afd333d..b7f00aaf 100644 --- a/Resources/views/backend/order/adyen_payment_method/view/list.js +++ b/Resources/views/backend/order/adyen_payment_method/view/list.js @@ -12,7 +12,7 @@ dataIndex: 'adyen_payment_order_payment', flex:1, sortable: false, - renderer: function(value, metaData, record) { + renderer: function (value, metaData, record) { return record.raw.adyen_payment_order_payment; } }); diff --git a/Subscriber/AccountPaymentSubscriber.php b/Subscriber/AccountPaymentSubscriber.php index ee1c74d0..0baa9953 100644 --- a/Subscriber/AccountPaymentSubscriber.php +++ b/Subscriber/AccountPaymentSubscriber.php @@ -69,7 +69,8 @@ public function beforeAccount(\Enlight_Event_EventArgs $args) $subject->Request()->setPost('register', ['payment' => $paymentId]); $subject->Request()->setPost( - 'adyenPayment', $this->shopwarePaymentMethodService->getAdyenMethod($payment) + 'adyenPayment', + $this->shopwarePaymentMethodService->getAdyenMethod($payment) ); } } diff --git a/Subscriber/CheckoutSubscriber.php b/Subscriber/CheckoutSubscriber.php index d633a0b8..913363c1 100644 --- a/Subscriber/CheckoutSubscriber.php +++ b/Subscriber/CheckoutSubscriber.php @@ -419,10 +419,12 @@ private function shouldRedirectToStep2(Shopware_Controllers_Frontend_Checkout $s $adyenMethods = $this->paymentMethodService->getPaymentMethods($countryCode, $currency, $value); $selectedType = $userData['additional']['user'][AdyenPayment::ADYEN_PAYMENT_PAYMENT_METHOD]; - $adyenMethods['paymentMethods'] = array_filter($adyenMethods['paymentMethods'], + $adyenMethods['paymentMethods'] = array_filter( + $adyenMethods['paymentMethods'], function ($element) use ($selectedType) { return ($element['type'] === $selectedType); - }); + } + ); if (!count($adyenMethods['paymentMethods'])) { return true; diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..1114cfa9 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,32 @@ + + + + + vendor/* + . + + *.js + + From cf52959404e0416d09243b11e4cd030671a18f0a Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Tue, 16 Jun 2020 13:45:18 +0200 Subject: [PATCH 4/9] PHPCS fixes --- Components/NotificationProcessor.php | 12 ++++++++++-- Subscriber/BackendConfigSubscriber.php | 4 +++- Subscriber/CheckoutSubscriber.php | 8 ++++---- Subscriber/FrontendPaymentNameSubscriber.php | 7 ++++--- phpcs.xml | 4 +++- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Components/NotificationProcessor.php b/Components/NotificationProcessor.php index f8dbb4aa..7a316eae 100644 --- a/Components/NotificationProcessor.php +++ b/Components/NotificationProcessor.php @@ -128,14 +128,22 @@ private function process(Notification $notification) 'message' => $exception->getMessage(), 'notificationId' => $exception->getNotification()->getId() ]); - yield new NotificationProcessorFeedback(false, "NotificationException: " . $exception->getMessage(), $notification); + yield new NotificationProcessorFeedback( + false, + "NotificationException: " . $exception->getMessage(), + $notification + ); } catch (\Exception $exception) { $status = NotificationStatus::STATUS_FATAL; $this->logger->notice('General Exception', [ 'exception' => $exception, 'notificationId' => $notification->getId() ]); - yield new NotificationProcessorFeedback(false, "General Exception: " . $exception->getMessage(), $notification); + yield new NotificationProcessorFeedback( + false, + "General Exception: " . $exception->getMessage(), + $notification + ); } } diff --git a/Subscriber/BackendConfigSubscriber.php b/Subscriber/BackendConfigSubscriber.php index a302eaf3..91063bc0 100644 --- a/Subscriber/BackendConfigSubscriber.php +++ b/Subscriber/BackendConfigSubscriber.php @@ -62,7 +62,9 @@ public function onBackendConfig(\Enlight_Event_EventArgs $args) /** @var Shopware_Controllers_Backend_Config $subject */ $subject = $args->getSubject(); - if ($subject->Request()->getActionName() === 'saveForm' && $subject->Request()->getParam('name') === AdyenPayment::NAME) { + if ($subject->Request()->getActionName() === 'saveForm' + && $subject->Request()->getParam('name') === AdyenPayment::NAME + ) { try { $this->originKeysService->generateAndSave(); } catch (AdyenException $e) { diff --git a/Subscriber/CheckoutSubscriber.php b/Subscriber/CheckoutSubscriber.php index 913363c1..f2d52dd5 100644 --- a/Subscriber/CheckoutSubscriber.php +++ b/Subscriber/CheckoutSubscriber.php @@ -114,8 +114,8 @@ public function __construct( public static function getSubscribedEvents() { return [ - 'Enlight_Controller_Action_PreDispatch_Frontend_Checkout' => 'CheckoutFrontendPreDispatch', - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'CheckoutFrontendPostDispatch', + 'Enlight_Controller_Action_PreDispatch_Frontend_Checkout' => 'checkoutFrontendPreDispatch', + 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'checkoutFrontendPostDispatch', 'sAdmin::sUpdatePayment::after' => 'sAdminAfterSUpdatePayment', 'sAdmin::sGetDispatchBasket::after' => 'sAdminAfterSGetDispatchBasket', ]; @@ -124,7 +124,7 @@ public static function getSubscribedEvents() /** * @param Enlight_Event_EventArgs $args */ - public function CheckoutFrontendPreDispatch(Enlight_Event_EventArgs $args) + public function checkoutFrontendPreDispatch(Enlight_Event_EventArgs $args) { $this->rewritePostPayment($args); $this->unsetPaymentSessions($args); @@ -134,7 +134,7 @@ public function CheckoutFrontendPreDispatch(Enlight_Event_EventArgs $args) * @param Enlight_Event_EventArgs $args * @throws AdyenException */ - public function CheckoutFrontendPostDispatch(Enlight_Event_EventArgs $args) + public function checkoutFrontendPostDispatch(Enlight_Event_EventArgs $args) { $this->checkFirstCheckoutStep($args); $this->rewritePaymentData($args); diff --git a/Subscriber/FrontendPaymentNameSubscriber.php b/Subscriber/FrontendPaymentNameSubscriber.php index db15a9c5..aba2c071 100644 --- a/Subscriber/FrontendPaymentNameSubscriber.php +++ b/Subscriber/FrontendPaymentNameSubscriber.php @@ -45,14 +45,14 @@ public function __construct( public static function getSubscribedEvents() { return [ - 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'CheckoutFrontendPostDispatch', + 'Enlight_Controller_Action_PostDispatch_Frontend_Checkout' => 'checkoutFrontendPostDispatch', ]; } /** * @param Enlight_Event_EventArgs $args */ - public function CheckoutFrontendPostDispatch(Enlight_Event_EventArgs $args) + public function checkoutFrontendPostDispatch(Enlight_Event_EventArgs $args) { $this->rewriteConfirmPaymentInfo($args); $this->rewriteFinishPaymentInfo($args); @@ -85,7 +85,8 @@ private function rewriteConfirmPaymentInfo(Enlight_Event_EventArgs $args) $userData['additional']['payment']['description'] = $adyenMethodName->getName(); $userData['additional']['payment']['additionaldescription'] = $adyenMethodName->getDescription(); - $userData['additional']['payment']['image'] = $this->shopwarePaymentMethodService->getAdyenImageByType($adyenType); + $userData['additional']['payment']['image'] = $this->shopwarePaymentMethodService + ->getAdyenImageByType($adyenType); $userData['additional']['payment']['type'] = $adyenType; $subject->View()->assign('sUserData', $userData); diff --git a/phpcs.xml b/phpcs.xml index 1114cfa9..0ecfd23d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -25,7 +25,9 @@ vendor/* - . + Controllers/* + AdyenPayment.php + *.js From b9fbf87e3390c617a9998833e319166d7f6d5daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20Campos?= Date: Tue, 16 Jun 2020 16:06:41 +0200 Subject: [PATCH 5/9] Update Components/IncomingNotificationManager.php Co-authored-by: Attila Kiss <42297201+cyattilakiss@users.noreply.github.com> --- Components/IncomingNotificationManager.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index 9a1d6055..784ffc6b 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -56,8 +56,10 @@ public function save(array $notificationItems) { foreach ($notificationItems as $notificationItem) { try { - $notification = $this->notificationBuilder->fromParams($notificationItem['NotificationRequestItem']); - $this->entityManager->persist($notification); + if (!empty($notificationItem['NotificationRequestItem']) { + $notification = $this->notificationBuilder->fromParams($notificationItem['NotificationRequestItem']); + $this->entityManager->persist($notification); + } } catch (InvalidParameterException $exception) { $this->logger->warning($exception->getMessage()); yield new NotificationItemFeedback($exception->getMessage(), $notificationItem); From 0bcb16a9a8a099ba973a3b500216fe3adec12e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20Campos?= Date: Tue, 16 Jun 2020 16:07:55 +0200 Subject: [PATCH 6/9] Update Models/ShopwareInfo.php --- Models/ShopwareInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/ShopwareInfo.php b/Models/ShopwareInfo.php index 32f743af..f0d045ca 100644 --- a/Models/ShopwareInfo.php +++ b/Models/ShopwareInfo.php @@ -39,7 +39,7 @@ public function getShopwareName() */ public function getPluginVersion() { - return "1.0.0"; + return "1.4.1"; } /** From 9fe2824d77ef572d18008d640957205b313999b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20Campos?= Date: Tue, 16 Jun 2020 16:09:45 +0200 Subject: [PATCH 7/9] Update ruleset.xml Co-authored-by: Attila Kiss <42297201+cyattilakiss@users.noreply.github.com> --- ruleset.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index 73d6ef40..01729796 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -2,5 +2,7 @@ PSR-2 standards - - \ No newline at end of file + + *.js + + From 0fe9c6ebcc53737e5a412715af3ef265e4382903 Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Tue, 16 Jun 2020 16:16:16 +0200 Subject: [PATCH 8/9] Fixes --- Components/Adyen/PaymentMethodService.php | 4 ++-- Components/IncomingNotificationManager.php | 2 +- Controllers/Backend/TestAdyenApi.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/Adyen/PaymentMethodService.php b/Components/Adyen/PaymentMethodService.php index 1becd1ec..a613135c 100644 --- a/Components/Adyen/PaymentMethodService.php +++ b/Components/Adyen/PaymentMethodService.php @@ -55,15 +55,15 @@ public function __construct( * @param string $countryCode * @param string $currency * @param int $value + * @param null $locale * @param bool $cache * @return array - * @throws AdyenException */ public function getPaymentMethods( $countryCode = null, $currency = null, - $locale = null, $value = null, + $locale = null, $cache = true ): array { $cacheKey = $this->getCacheKey($countryCode ?? '', $currency ?? '', (string)$value ?? ''); diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index 784ffc6b..a2f9fb62 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -56,7 +56,7 @@ public function save(array $notificationItems) { foreach ($notificationItems as $notificationItem) { try { - if (!empty($notificationItem['NotificationRequestItem']) { + if (!empty($notificationItem['NotificationRequestItem'])) { $notification = $this->notificationBuilder->fromParams($notificationItem['NotificationRequestItem']); $this->entityManager->persist($notification); } diff --git a/Controllers/Backend/TestAdyenApi.php b/Controllers/Backend/TestAdyenApi.php index 2ea00ed3..a5badba8 100644 --- a/Controllers/Backend/TestAdyenApi.php +++ b/Controllers/Backend/TestAdyenApi.php @@ -17,7 +17,7 @@ public function runAction() $responseText = 'Missing API configuration. Save the configuration form before testing'; } - if (!empty($paymentMethodService->getPaymentMethods('BE', 'EUR', 'nl_NL', 20, false))) { + if (!empty($paymentMethodService->getPaymentMethods('BE', 'EUR', 20, 'nl_NL', false))) { $this->response->setHttpResponseCode(Response::HTTP_OK); $responseText = 'Adyen API connected'; } From 61bf7864a1864ed434207cf403863a6f4e7f5609 Mon Sep 17 00:00:00 2001 From: acampos1916 Date: Tue, 16 Jun 2020 16:20:12 +0200 Subject: [PATCH 9/9] Fixes --- Components/IncomingNotificationManager.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Components/IncomingNotificationManager.php b/Components/IncomingNotificationManager.php index a2f9fb62..68fb7481 100644 --- a/Components/IncomingNotificationManager.php +++ b/Components/IncomingNotificationManager.php @@ -57,7 +57,9 @@ public function save(array $notificationItems) foreach ($notificationItems as $notificationItem) { try { if (!empty($notificationItem['NotificationRequestItem'])) { - $notification = $this->notificationBuilder->fromParams($notificationItem['NotificationRequestItem']); + $notification = $this->notificationBuilder->fromParams( + $notificationItem['NotificationRequestItem'] + ); $this->entityManager->persist($notification); } } catch (InvalidParameterException $exception) {