From 42e5231794f0b96086f54dc15f9b4eb62efc9b9a Mon Sep 17 00:00:00 2001 From: "release@novalnetsolutions.com" Date: Wed, 24 Apr 2024 18:14:47 +0530 Subject: [PATCH] first commit --- README.md | 117 ++ changelog.txt | 137 ++ composer.json | 43 + .../NovalnetPaymentTransactionCollection.php | 42 + .../NovalnetPaymentTransactionDefinition.php | 70 + .../NovalnetPaymentTransactionEntity.php | 212 +++ .../Administration/AdminController.php | 260 ++++ .../Storefront/FrontendController.php | 174 +++ .../Storefront/WebhookController.php | 1082 ++++++++++++++ src/Helper/NovalnetHelper.php | 1161 +++++++++++++++ src/Helper/NovalnetOrderTransactionHelper.php | 1318 +++++++++++++++++ src/Installer/MediaProvider.php | 83 ++ src/Installer/PaymentMethodInstaller.php | 1130 ++++++++++++++ ...ation1678945880AddNovalnetPaymentTable.php | 44 + src/NovalnetPayment.php | 102 ++ .../app/administration/src/acl/index.js | 67 + ...ovalnet-payment-api-credentials.service.js | 197 +++ .../src/init/api-service.init.js | 11 + src/Resources/app/administration/src/main.js | 3 + .../novalnet-payment-credentials/index.js | 299 ++++ .../novalnet-payment-credentials.html.twig | 202 +++ .../novalnet-payment-credentials.scss | 8 + .../novalnet-payment-settings-icon/index.js | 13 + .../novalnet-payment-settings-icon.html.twig | 3 + .../component/sw-order-general-info/index.js | 84 ++ .../sw-order-general-info.html.twig | 18 + .../component/sw-order-user-card/index.js | 83 ++ .../sw-order-user-card.html.twig | 10 + .../index.js | 97 ++ ...valnet-payment-book-amount-modal.html.twig | 35 + .../index.js | 87 ++ ...-payment-instalment-cancel-modal.html.twig | 32 + .../index.js | 104 ++ ...payment-manage-transaction-modal.html.twig | 32 + .../novalnet-payment-refund-modal/index.js | 112 ++ .../novalnet-payment-refund-modal.html.twig | 40 + .../view/sw-order-create-details/index.js | 188 +++ .../sw-order-create-details.html.twig | 24 + .../view/sw-order-create-general/index.js | 83 ++ .../view/sw-order-detail-details/index.js | 373 +++++ .../sw-order-detail-details.html.twig | 317 ++++ .../sw-order-detail-details.scss | 103 ++ .../src/module/novalnet-payment/index.js | 67 + .../page/novalnet-payment-settings/index.js | 221 +++ .../novalnet-payment-settings.html.twig | 98 ++ .../novalnet-payment-settings.scss | 50 + .../novalnet-payment/snippet/de_DE.json | 138 ++ .../novalnet-payment/snippet/en_GB.json | 139 ++ .../app/administration/static/img/plugin.png | Bin 0 -> 2375 bytes .../dist/storefront/js/novalnet-payment.js | 1 + .../js/novalnet-payment/novalnet-payment.js | 1 + src/Resources/app/storefront/src/main.js | 8 + .../novalnet-payment.plugin.js | 315 ++++ .../app/storefront/src/scss/base.scss | 138 ++ src/Resources/config/plugin.png | Bin 0 -> 2375 bytes src/Resources/config/routes.xml | 9 + src/Resources/config/services.xml | 111 ++ .../administration/css/novalnet-payment.css | 5 + .../administration/js/novalnet-payment.js | 2 + .../administration/js/novalnet-payment.js.map | 1 + .../public/administration/plugin.png | Bin 0 -> 2375 bytes .../static/css/novalnet-payment.css.map | 1 + src/Resources/public/static/img/plugin.png | Bin 0 -> 2375 bytes .../public/static/js/novalnet-payment.js.map | 1 + .../storefront/assets/img/novalnetpay.png | Bin 0 -> 1759 bytes .../snippet/novalnet-payment.de-DE.json | 92 ++ .../snippet/novalnet-payment.en-GB.json | 93 ++ .../views/administration/index.html.twig | 6 + .../views/documents/invoice.html.twig | 43 + .../payment/payment-method.html.twig | 52 + .../novalnet-subscription/detail.html.twig | 40 + .../order-history/order-detail.html.twig | 77 + .../order-history/order-item.html.twig | 25 + .../page/account/order/index.html.twig | 22 + .../page/checkout/confirm/index.html.twig | 7 + .../checkout/finish/finish-details.html.twig | 49 + src/Service/NovalnetPayment.php | 621 ++++++++ .../Administration/OrderEventSubscriber.php | 168 +++ src/Subscriber/Core/FrameEventSubscriber.php | 62 + .../NovalnetOrderFinishLoadedEvent.php | 69 + .../Storefront/PaymentEventSubscriber.php | 86 ++ src/Twig/Filter/NovalnetFilter.php | 119 ++ 82 files changed, 11537 insertions(+) create mode 100644 README.md create mode 100644 changelog.txt create mode 100644 composer.json create mode 100644 src/Content/PaymentTransaction/NovalnetPaymentTransactionCollection.php create mode 100644 src/Content/PaymentTransaction/NovalnetPaymentTransactionDefinition.php create mode 100644 src/Content/PaymentTransaction/NovalnetPaymentTransactionEntity.php create mode 100644 src/Controller/Administration/AdminController.php create mode 100644 src/Controller/Storefront/FrontendController.php create mode 100644 src/Controller/Storefront/WebhookController.php create mode 100644 src/Helper/NovalnetHelper.php create mode 100644 src/Helper/NovalnetOrderTransactionHelper.php create mode 100644 src/Installer/MediaProvider.php create mode 100644 src/Installer/PaymentMethodInstaller.php create mode 100644 src/Migration/Migration1678945880AddNovalnetPaymentTable.php create mode 100644 src/NovalnetPayment.php create mode 100644 src/Resources/app/administration/src/acl/index.js create mode 100644 src/Resources/app/administration/src/core/service/api/novalnet-payment-api-credentials.service.js create mode 100644 src/Resources/app/administration/src/init/api-service.init.js create mode 100644 src/Resources/app/administration/src/main.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.scss create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/novalnet-payment-settings-icon.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/sw-order-general-info.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/novalnet-payment-book-amount-modal.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/novalnet-payment-instalment-cancel-modal.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/novalnet-payment-manage-transaction-modal.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/novalnet-payment-refund-modal.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/sw-order-create-details.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-general/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.scss create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/index.js create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.html.twig create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.scss create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/snippet/de_DE.json create mode 100644 src/Resources/app/administration/src/module/novalnet-payment/snippet/en_GB.json create mode 100644 src/Resources/app/administration/static/img/plugin.png create mode 100644 src/Resources/app/storefront/dist/storefront/js/novalnet-payment.js create mode 100644 src/Resources/app/storefront/dist/storefront/js/novalnet-payment/novalnet-payment.js create mode 100644 src/Resources/app/storefront/src/main.js create mode 100644 src/Resources/app/storefront/src/novalnet-payment/novalnet-payment.plugin.js create mode 100644 src/Resources/app/storefront/src/scss/base.scss create mode 100644 src/Resources/config/plugin.png create mode 100644 src/Resources/config/routes.xml create mode 100644 src/Resources/config/services.xml create mode 100644 src/Resources/public/administration/css/novalnet-payment.css create mode 100644 src/Resources/public/administration/js/novalnet-payment.js create mode 100644 src/Resources/public/administration/js/novalnet-payment.js.map create mode 100644 src/Resources/public/administration/plugin.png create mode 100644 src/Resources/public/static/css/novalnet-payment.css.map create mode 100644 src/Resources/public/static/img/plugin.png create mode 100644 src/Resources/public/static/js/novalnet-payment.js.map create mode 100644 src/Resources/public/storefront/assets/img/novalnetpay.png create mode 100644 src/Resources/snippet/novalnet-payment.de-DE.json create mode 100644 src/Resources/snippet/novalnet-payment.en-GB.json create mode 100644 src/Resources/views/administration/index.html.twig create mode 100644 src/Resources/views/documents/invoice.html.twig create mode 100644 src/Resources/views/storefront/component/payment/payment-method.html.twig create mode 100644 src/Resources/views/storefront/page/account/novalnet-subscription/detail.html.twig create mode 100644 src/Resources/views/storefront/page/account/order-history/order-detail.html.twig create mode 100644 src/Resources/views/storefront/page/account/order-history/order-item.html.twig create mode 100644 src/Resources/views/storefront/page/account/order/index.html.twig create mode 100644 src/Resources/views/storefront/page/checkout/confirm/index.html.twig create mode 100644 src/Resources/views/storefront/page/checkout/finish/finish-details.html.twig create mode 100644 src/Service/NovalnetPayment.php create mode 100644 src/Subscriber/Administration/OrderEventSubscriber.php create mode 100644 src/Subscriber/Core/FrameEventSubscriber.php create mode 100644 src/Subscriber/Storefront/NovalnetOrderFinishLoadedEvent.php create mode 100644 src/Subscriber/Storefront/PaymentEventSubscriber.php create mode 100644 src/Twig/Filter/NovalnetFilter.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1f3d8f --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# Novalnet payment plugin for Shopware 6 + +Novalnet payment plugin for Shopware 6 simplifies your daily work by automating the entire payment process, from checkout till collection. The Shopware 6 payment plugin is designed to help you increase your sales by offering various international and local payment methods. + +## Why Shopware 6 with Novalnet? + +Shopware 6 is the next generation shop system based on advanced hook system. With the open source and commercial versions, users can easily create Shopware 6 storefronts based on Twig and Bootstrap technologies. Novalnet’s Shopware 6 payment plugin supports international and local payments including payment guarantee and cash payment particularly for Shopware. We encrypt our end customer data (strong encryption) to free you from data protection concerns and from payment related licenses. + +## Advantages of Shopware 6 Payment plugin +- Easy configuration for all payment methods - international and local +- One platform for all payment types and related services +- Complete automation of all payment processes +- More than 50 fraud prevention modules integrated to prevent risk in real-time +- Effortless configuration of risk management with fraud prevention +- Comprehensive affiliate system with automated split conversion of transaction on revenue sharing +- No PCI DSS certification required when using our payment module +- Real-time monitoring of the payment methods & transaction flows +- Multilevel claims management with integrated handover to collection and various export functions for the accounting +- Automated e-mail notification function concerning payment status reports +- Clear real-time overview and monitoring of payment status +- Automated bookkeeping report in XML, SOAP, CSV, MT940 +- Seamless and fast integration of the payment plugin +- Secure SSL- encoded gateways +- Seamless checkout Iframe integration +- One click shopping supported Credit/Debit Cards & Direct Debit SEPA +- Zero amount authorization supported for Credit/Debit Cards, Direct Debit SEPA, Direct Debit ACH, Apple Pay & Google Pay payments +- Easy way of confirmation and cancellation of on-hold transactions for Direct Debit SEPA, Direct Debit SEPA with payment guarantee, Instalment payment for Direct Debit SEPA, Credit/Debit Cards, Invoice, Invoice with payment guarantee, Instalment payment for Invoice, Prepayment, PayPal, Apple Pay & Google Pay +- Refund option for Credit/Debit Cards, Direct Debit SEPA, Direct Debit ACH, Direct Debit SEPA with payment guarantee, Instalment by Direct Debit SEPA, Invoice, Invoice with payment guarantee, Instalment by Invoice, Prepayment, Barzahlen/viacash, Sofort, iDEAL, eps, giropay, PayPal, MB Way, Przelewy24, PostFinance Card, PostFinance E-Finance, Bancontact, Apple Pay, Google Pay, Online bank transfer, Alipay, WeChat pay, Trustly, Blik & Payconiq +- Order creation process handled for shop admin for the Direct Debit SEPA, Invoice, Prepayment, Barzahlen/viacash & Multibanco +- Responsive templates + +## Compatibility +Shopware 6 payment plugin is compatible with below technical capabilities. + +- [x] Shopware versions 6.6.0.0 - 6.6.1.1 +- [x] Linux based OS with Apache 2.2 or 2.4 +- [x] PHP 7.2.0 or higher +- [x] MySQL 5.7 or higher + +## Supported payment methods + +- Direct Debit SEPA +- Direct Debit ACH +- Credit/Debit Cards +- Apple Pay +- Google Pay +- Invoice +- Prepayment +- Invoice with payment guarantee +- Direct Debit SEPA with payment guarantee +- Instalment by Invoice +- Instalment by Direct Debit SEPA +- iDEAL +- Sofort +- giropay +- Barzahlen/viacash +- Przelewy24 +- eps +- PayPal +- MB Way +- PostFinance Card +- PostFinance E-Finance +- Bancontact +- Multibanco +- Online bank transfer +- Alipay +- WeChat Pay +- Trustly +- Blik +- Payconiq + +## Installation via Composer + +#### Follow the below steps and run each command in your terminal from the shop root directory + ##### 1. Run the below command to upload the payment plugin + ``` + composer require novalnet/shopware6-payment + ``` + ##### 2. Run the below command to refresh the payment plugin + ``` + php bin/console plugin:refresh + ``` + ##### 3. Run the below command to install and activate the payment plugin + ``` + php bin/console plugin:install --activate --clearCache NovalnetPayment + ``` +## Quick Installation via plugin upload +Follow the below steps to install the payment plugin for Shopware 6 in the shop system. + +1. **Download** the plugin from here or contact us. + +2. **Login** to shop backend, move to: + - Extensions + - My extensions + - Upload extensions + +3. **Upload** the Shopware 6 payment Apps from Novalnet to the shop + +4. Click **Install** to install the Novalnet Shopware 6 Payment Apps + +## Documentation & Support +For more information about the integration, please get in touch with us at sales@novalnet.de or +49 89 9230683-20 or by contacting us here. + +Novalnet AG
+Zahlungsinstitut (ZAG)
+Gutenbergstraße 7
+D-85748 Garching
+Deutschland
+E-mail: sales@novalnet.de
+Tel: +49 89 9230683-20
+Web: www.novalnet.de + +## Licenses + +As a European Payment institution, Novalnet holds all necessary payment licenses to accept and process payments worldwide. We also comply with European data protection regulations to guarantee advanced data protection worldwide. + +See here for [Freeware License Agreement](https://www.novalnet.com/payment-plugins-free-license/). diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..5375e57 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,137 @@ +*** Changelog *** + += 13.5.1 - 2024.04.24 = +* Enhanced - Optimised Novalnet Payment plugin to support Novalnet subscription plugin features +* Enhanced - Optimized the Callback/Webhook process for fetching Dynamic IP + += 13.5.0 - 2024.04.11 = +* Fix - Compatibility for Shopware 6.6 +* Enhanced - Novalnet Order confirmation email templates now support attachments and customized headers and footers + += 13.4.1 - 2024.02.15 = +* Fix - Adjusted webhook to handle multiple transactions on the same order +* Fix - Removed session handling during API and Administration processes +* Fix - Dynamically displays the payment name in the language of your order for both the Invoice PDF and the Shop Order Confirmation Email +* Fix - Handled credit/debit card payments seamlessly in case of communication failures during the subscription free trial product purchase +* Fix - Multiple submit buttons are now supported in the checkout process + += 13.4.0 - 2023.12.12 = +* New - Implemented configuration options for On-hold and Completion payment statuses +* New - Implemented Zero amount authorization for Apple Pay, Google Pay and Direct Debit ACH payments +* Fix - Order update emails will dynamically display payment method name associated with an order +* Fix - Console error when an undefined payment variable is processed during iFrame initialization + += 13.3.0 - 2023.11.09 = +* New - Implemented MB Way payment +* Fix - Callback/webhook optimized to execute without order number +* Fix - Appropriate theme name fetched from the shop theme object for checkout page loading + += 13.2.0 - 2023.09.08 = +* Fix - Payment Plugin build executed for proper installation +* Enhanced - Optimised Novalnet Payment plugin to support Novalnet subscription plugin features + += 13.0.0 - 2023.08.25 = +* Major version release: We've improved the seamless customer experience and brand configurations in the Novalnet Merchant Admin Portal to make it easier to preview and customise the checkout page + += 12.5.7 - 2023.07.21 = +* Enhanced - Additional payment statuses 'Authorized' and 'Paid partially' are handled for payment specific transactions + += 12.5.6 - 2023.06.21 = +* Enhanced - Optimised Novalnet Payment plugin to support Novalnet subscription plugin features + += 12.5.5 - 2023.06.09 = +* Fix - Compatibility for Shopware 6.5.0.0 +* Enhanced - For IBAN-specific countries, the BIC field required for the Gibraltar country for the payments like Direct Debit SEPA, Direct Debit SEPA with payment guarantee and Instalment by Direct Debit SEPA + += 12.5.4 - 2023.05.23 = +* Fix - Supported payment methods are now listed in the My Account section +* Enhanced - Optimised Novalnet Payment plugin to support Novalnet subscription plugin features + += 12.5.3 - 2023.05.09 = +* Fix - Invoice PDF gets updated with the recent transaction details during PDF generation + += 12.5.2 - 2023.02.09 = +* New - Added ACL feature for Novalnet payment plugin +* Fix - Adjusted E-mail template as per the Shopware standard +* Fix - Callback/webhook comments are handled based on the initial transaction language +* Removed - Google Pay button theme configuration + += 12.5.1 - 2022.11.15 = +* Fix - No custom payment logo replacement during the plugin update +* Enhanced - Adjusted novalnet payment plugin to support Multibanco payment for the Novalnet subscription plugin +* Removed - Status based Capture, Void and Refund actions + += 12.5.0 - 2022.10.21 = +* New - Implemented Google pay, Trustly, WeChat and Alipay payments +* New - Implemented Zero amount authorization for the payments Credit/Debit card and Direct Debit SEPA +* New - Updated cart details to PayPal +* Enhanced - Invoice payment name is updated for German translation +* Enhanced - Callback/Webhook events has been optimized as per the new testcase +* Removed - One-click shopping for PayPal payment + += 12.4.0 - 2022.07.26 = +* New - Implemented Online bank transfer payment +* New - Implemented Apple pay payment +* Fix - Novalnet payment plugin adjusted to support for Amazon pay during activation +* Enhanced - For IBAN-specific countries, the BIC field displayed for payments like Direct Debit SEPA, Direct Debit SEPA with payment guarantee & Instalment by Direct Debit SEPA + += 12.3.0 - 2022.05.10 = +* New - Order creation process handled from the shop admin panel for the Invoice, Prepayment, Barzahlen/viacash & Multibanco +* New - Added "state" parameter in the payment request +* Fix - Compatibility for Shopware 6.4.10.1 +* Fix - Credit/Debit Cards error message and Checkout button +* Enhanced - Updated payment logo for sofort payment + += 12.2.2 - 2022.02.25 = +* Fix - Adjusted payment plugin to translate Novalnet transaction details with the same language updated in the domain URL +* Fix - Refund API request is restricted for multiple transactions with the same order number + += 12.2.1 - 2022.02.03 = +* Fix - Compatibility for Shopware 6.3 using Promotion code + += 12.2.0 - 2021.11.17 = +* Fix - Compatibility for PHP version 8.0 +* New - Configuration to send order confirmation email to end-customer +* Enhance - Payment plugin installation via composer + += 12.1.2 - 2021.08.13 = +* Fix - Order confirmation email sent to end customers for the newly created domain URLs + += 12.1.1 - 2021.07.26 = +* Fix - Compatibility for Shopware 6.4 series +* Fix - Callback has been optimized as per the new testcase + += 12.1.0 - 2021.05.19 = +* New - Implemented instalment payments for Direct Debit SEPA and Invoice +* Fix - Saved card/account information can be deleted in Safari and Firefox browsers +* Fix - Placed the saved card/account details view above change payment method button +* Fix - Webhook: Follow up transaction call restricted for on-hold payments +* Enhanced - Vendor script has been optimized + += 12.0.0 - 2021.04.01 = +Major version release: Entire restructure & optimization of older version with usability, additional features and customizations + += 1.0.5 - 2021.02.05 = +* Fix - Orders get confined while adding multiple order comments into the same order +* Fix - Adjusted payment plugin to display error message for guarantee payments when Force Non-Guarantee payment option was disabled + += 1.0.4 - 2021.01.05 = +* Fix - Date of birth validation regulated on checkout page +* Fix - Adjusted the redirect payment methods to support Sales channels +* Enhanced - Barzahlen payment name and logo + += 1.0.3 - 2020.12.05 = +* Fix - Adjusted JavaScript for displaying the "Submit" button on the checkout page +* Fix - Adjusted JavaScript for throwing error message on the checkout page for Invalid Credit Card details +* Fix - Mailer configuration is not mandatory now for order success page + += 1.0.2 - 2020.11.11 = +* Fix - Added Datepicker by adjusting Javascript for macOS Safari browser + += 1.0.1 - 2020.10.06 = +* Fix - Sitemap XML tag issue +* Fix - Adjusted the redirect payment methods to support Sales channels +* Enhanced - Barzahlen payment name and logo + += 1.0.0 - 2021.01.05 = + - New release diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..313a738 --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "novalnet/novalnet-payment", + "description": "PCI Compliant, seamless integration with the various types of payment and payment-related services integrated into one unique platform", + "version": "v13.5.1", + "type": "shopware-platform-plugin", + "license": "MIT", + "authors": [ + { + "name": "Novalnet AG", + "homepage": "https://www.novalnet.de" + } + ], + "extra": { + "shopware-plugin-class": "Novalnet\\NovalnetPayment\\NovalnetPayment", + "plugin-icon": "src/Resources/config/plugin.png", + "copyright": "(c) by Novalnet AG", + "label": { + "de-DE": "Novalnet Zahlung", + "en-GB": "Novalnet Payments" + }, + "description": { + "de-DE": "PCI-konforme Zahlungsabwicklung mit einer Vielzahl an Payment-Services und nahtloser Integration für eine einfache Anbindung.", + "en-GB": "PCI compliant payment solution, covering a full scope of payment services and seamless integration for easy adaptability." + }, + "manufacturerLink": { + "de-DE": "https://www.novalnet.de", + "en-GB": "https://www.novalnet.com" + }, + "supportLink": { + "de-DE": "https://www.novalnet.de", + "en-GB": "https://www.novalnet.com" + } + }, + "autoload": { + "psr-4": { + "Novalnet\\NovalnetPayment\\": "src/" + } + }, + "require": { + "shopware/core": "^6.6.0", + "shopware/storefront": "^6.6.0" + } +} diff --git a/src/Content/PaymentTransaction/NovalnetPaymentTransactionCollection.php b/src/Content/PaymentTransaction/NovalnetPaymentTransactionCollection.php new file mode 100644 index 0000000..70dadea --- /dev/null +++ b/src/Content/PaymentTransaction/NovalnetPaymentTransactionCollection.php @@ -0,0 +1,42 @@ +setFlags(new PrimaryKey(), new Required()), + (new IntField('tid', 'tid'))->setFlags(new Required()), + (new StringField('payment_type', 'paymentType'))->setFlags(new Required()), + (new FloatField('amount', 'amount'))->setFlags(), + (new StringField('currency', 'currency'))->setFlags(), + (new FloatField('paid_amount', 'paidAmount'))->setFlags(), + (new FloatField('refunded_amount', 'refundedAmount'))->setFlags(), + (new StringField('gateway_status', 'gatewayStatus'))->setFlags(new Required()), + (new NumberRangeField('order_no', 'orderNo'))->setFlags(new Required()), + (new NumberRangeField('customer_no', 'customerNo'))->setFlags(new Required()), + (new LongTextField('additional_details', 'additionalDetails'))->setFlags(), + (new StringField('token_info', 'tokenInfo'))->setFlags(), + ]); + } +} diff --git a/src/Content/PaymentTransaction/NovalnetPaymentTransactionEntity.php b/src/Content/PaymentTransaction/NovalnetPaymentTransactionEntity.php new file mode 100644 index 0000000..7470c41 --- /dev/null +++ b/src/Content/PaymentTransaction/NovalnetPaymentTransactionEntity.php @@ -0,0 +1,212 @@ +tid; + } + + public function setTid(int $tid): void + { + $this->tid = $tid; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getPaymentType(): string + { + return $this->paymentType; + } + + public function setPaymentType(string $paymentType): void + { + $this->paymentType = $paymentType; + } + + public function getAmount(): ?int + { + return (int) $this->amount; + } + + public function setAmount(int $amount): void + { + $this->amount = $amount; + } + + public function getCurrency(): ?string + { + return (string) $this->currency; + } + + public function setCurrency(string $currency): void + { + $this->currency = $currency; + } + + public function getPaidAmount(): ? int + { + return (int) $this->paidAmount; + } + + public function setPaidAmount(int $paidAmount): void + { + $this->paidAmount = $paidAmount; + } + + public function getRefundedAmount(): ?int + { + return (int) $this->refundedAmount; + } + + public function setRefundedAmount(int $refundedAmount): void + { + $this->refundedAmount = $refundedAmount; + } + + public function getGatewayStatus(): string + { + return $this->gatewayStatus; + } + + public function setGatewayStatus(string $gatewayStatus): void + { + $this->gatewayStatus = $gatewayStatus; + } + + public function getOrderNo(): string + { + return $this->orderNo; + } + + public function setOrderNo(string $orderNo): void + { + $this->orderNo = $orderNo; + } + + public function getCustomerNo(): string + { + return $this->customerNo; + } + + public function setCustomerNo(string $customerNo): void + { + $this->customerNo = $customerNo; + } + + public function getAdditionalDetails(): ?string + { + return $this->additionalDetails; + } + + public function setAdditionalDetails(string $additionalDetails): void + { + $this->additionalDetails = $additionalDetails; + } + + public function getTokenInfo(): ?string + { + return $this->tokenInfo; + } + + public function setTokenInfo(string $tokenInfo): void + { + $this->tokenInfo = $tokenInfo; + } +} diff --git a/src/Controller/Administration/AdminController.php b/src/Controller/Administration/AdminController.php new file mode 100644 index 0000000..252c92d --- /dev/null +++ b/src/Controller/Administration/AdminController.php @@ -0,0 +1,260 @@ + ['api']])] +class AdminController extends AbstractController +{ + /** + * @var NovalnetHelper + */ + private $helper; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var NovalnetOrderTransactionHelper + */ + private $transactionHelper; + + /** + * Constructs a `AdminController` + * + * @param NovalnetHelper $helper + * @param TranslatorInterface $translator + * @param NovalnetOrderTransactionHelper $transactionHelper + + */ + public function __construct( + NovalnetHelper $helper, + TranslatorInterface $translator, + NovalnetOrderTransactionHelper $transactionHelper + ) { + $this->helper = $helper; + $this->translator = $translator; + $this->transactionHelper = $transactionHelper; + } + + + #[Route(path: '/api/_action/novalnet-payment/transaction-amount', name: 'api.action.novalnet.payment.transaction.amount', methods: ['POST'])] + public function getNovalnetData(Request $request, Context $context): JsonResponse + { + $novalnetTransactionData = []; + $orderNumber = $request->get('orderNumber'); + if (!empty($orderNumber)) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + if (!empty($transactionData)) { + $transactionData->setPaymentType($this->helper->getUpdatedPaymentType($transactionData->getpaymentType())); + $novalnetTransactionData = ['data' => $transactionData ]; + } + } + return new JsonResponse($novalnetTransactionData); + } + + #[Route(path: '/api/_action/novalnet-payment/refund-amount', name: 'api.action.novalnet.payment.refund.amount', methods: ['POST'])] + public function refundAmount(Request $request, Context $context): JsonResponse + { + $refundResponse = []; + $orderNumber = $request->get('orderNumber'); + + if (!empty($orderNumber)) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + $refundAmount = (int) round($request->get('refundAmount')); + + if (!empty($transactionData)) { + $orderReference = $this->transactionHelper->getOrder($orderNumber, $context); + $localeCode = $this->helper->getLocaleFromOrder($orderReference->getOrderId()); + + if ((int) $transactionData->getRefundedAmount() >= (int) $transactionData->getAmount()) { + $refundResponse = ['result' => ['status_text' => $this->translator->trans('NovalnetPayment.text.refundAlreadyExists', [], null, $localeCode)]]; + } elseif ($refundAmount > (int) $transactionData->getAmount()) { + $refundResponse = ['result' => ['status_text' => $this->translator->trans('NovalnetPayment.text.invalidRefundAmount', [], null, $localeCode)]]; + } elseif (!empty($orderReference)) { + $refundResponse = $this->transactionHelper->refundTransaction($transactionData, $orderReference, $context, $refundAmount, $request); + } + } + } + return new JsonResponse($refundResponse); + } + + #[Route(path: '/api/_action/novalnet-payment/manage-payment', name: 'api.action.novalnet.payment.manage.payment', methods: ['POST'])] + public function managePayment(Request $request, Context $context): JsonResponse + { + $managePayment = []; + $orderNumber = $request->get('orderNumber'); + if (!empty($orderNumber)&& !empty($request->get('status'))) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + + if (!empty($transactionData)) { + $orderReference = $this->transactionHelper->getOrder($orderNumber, $context); + $status = $request->get('status') == '100' ? 'transaction_capture' : 'transaction_cancel'; + $managePayment = !empty($orderReference) ? $this->transactionHelper->manageTransaction($transactionData, $orderReference, $context, $status) : []; + } + } + return new JsonResponse($managePayment); + } + + #[Route(path: '/api/_action/novalnet-payment/book-amount', name: 'api.action.novalnet.payment.book.amount', methods: ['POST'])] + public function bookAmount(Request $request, Context $context): JsonResponse + { + $bookResponse = []; + $orderNumber = $request->get('orderNumber'); + + if (!empty($orderNumber)) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + $bookAmount = (int) round($request->get('bookAmount')); + + if (!empty($transactionData)) { + $orderEntity = $this->transactionHelper->getOrderEntity($orderNumber, $context); + $bookResponse = !empty($orderEntity) ? $this->transactionHelper->bookOrderAmount($transactionData, $orderEntity, $context, $bookAmount) : []; + } + } + return new JsonResponse($bookResponse); + } + + + #[Route(path: '/api/_action/novalnet-payment/novalnet-paymentmethod', name: 'api.action.novalnet.payment.novalnet.paymentmethod', methods: ['POST'])] + public function getNovalnetPaymentMethodName(Request $request, Context $context): JsonResponse + { + $novalnetPaymentName = []; + $orderNumber = $request->get('orderNumber'); + if (!empty($orderNumber)) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + $additionalData = (!empty($transactionData) && !empty($transactionData->getAdditionalDetails())) ? $this->helper->unserializeData($transactionData->getAdditionalDetails()) : [] ; + if (!empty($additionalData) && isset($additionalData['payment_name']) && !empty($additionalData['payment_name'])) { + $novalnetPaymentName = ['paymentName' => $additionalData['payment_name']]; + } + } + + return new JsonResponse($novalnetPaymentName); + } + + #[Route(path: '/api/_action/novalnet-payment/instalment-cancel', name: 'api.action.novalnet.payment.instalment.cancel', methods: ['POST'])] + public function instalmentCancel(Request $request, Context $context): JsonResponse + { + $instalmentCancel = []; + $orderNumber = $request->get('orderNumber'); + + if (!empty($orderNumber) && !empty($request->get('cancelType'))) { + $transactionData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $context); + + if (!empty($transactionData)) { + $orderReference = $this->transactionHelper->getOrder($orderNumber, $context); + $instalmentCancel = !empty($orderReference) ? $this->transactionHelper->instalmentCancelType($transactionData, $orderReference, $context, $request) : []; + } + } + return new JsonResponse($instalmentCancel); + } + + + #[Route(path: '/api/_action/novalnet-payment/load-payment-form', name: 'api.action.novalnet.payment.load.payment.form', methods: ['POST'])] + public function loadNovalnetPaymentForm(Request $request, Context $context): JsonResponse + { + $response = ['result' => '']; + if (!empty($request->get('shippingaddress')) && !empty($request->get('billingaddress')) && !empty($request->get('amount')) && !empty($request->get('currency')) && !empty($request->get('customer'))) { + $requiredFields = ['amount' => $request->get('amount') * 100, 'currency' => $request->get('currency')]; + + $response = $this->helper->getNovalnetIframeResponse($request->get('customer')['salesChannel']['id'], $request->get('customer')['id'], $requiredFields, $context, 'seamless_payment', 'BACKEND'); + } + + return new JsonResponse($response); + } + + #[Route(path: '/api/_action/novalnet-payment/payment-value-data', name: 'api.action.novalnet.payment.payment.value.data', methods: ['POST'])] + public function paymentValueData(Request $request, Context $context): JsonResponse + { + $response = []; + if (!empty($request->get('value')) && !empty($request->get('customer'))) { + $paymentDetails = $this->helper->unserializeData($request->get('value')); + $result = $this->helper->orderBackendPaymentData($paymentDetails, $request->get('customer'), $context); + $response = ['success' => $result ]; + } + + return new JsonResponse($response); + } + + + #[Route(path: '/api/_action/novalnet-payment/validate-api-credentials', name: 'api.action.novalnet.payment.validate.api.credentials', methods: ['POST'])] + public function validateApiCredentials(Request $request, Context $context): JsonResponse + { + $data = []; + + if ($request->get('clientId') && $request->get('accessKey')) { + $parameters['merchant'] = [ + 'signature' => $request->get('clientId'), + ]; + $parameters['custom'] = [ + 'lang' => $this->helper->getLocaleCodeFromContext($context), + ]; + + $response = $this->helper->sendPostRequest($parameters, $this->helper->getActionEndpoint('merchant_details'), $request->get('accessKey')); + $data['serverResponse'] = $response; + if (isset($response['result']['status']) && $response['result']['status'] == 'SUCCESS') { + if (!empty($response['merchant']['tariff'])) { + $tariffs = []; + foreach ($response['merchant']['tariff'] as $key => $values) { + $tariffs[] = [ + 'id' => $key, + 'name' => $values['name'], + ]; + } + sort($tariffs); + $data ['tariffResponse'] = $tariffs; + } + } + } + return new JsonResponse($data); + } + + #[Route(path: '/api/_action/novalnet-payment/webhook-url-configure', name: 'api.action.novalnet.payment.webhook.url.configure', methods: ['POST'])] + public function configureWebhook(Request $request, Context $context): JsonResponse + { + $response = ['result' => '']; + if (!empty($request->get('url')) && !empty($request->get('productActivationKey')) && !empty($request->get('paymentAccessKey'))) { + $parameters = [ + 'merchant' => [ + 'signature' => $request->get('productActivationKey'), + ], + 'webhook' => [ + 'url' => $request->get('url'), + ], + 'custom' => [ + 'lang' => $this->helper->getLocaleCodeFromContext($context), + ], + ]; + + $response = $this->helper->sendPostRequest($parameters, $this->helper->getActionEndpoint('webhook_configure'), $request->get('paymentAccessKey')); + } + return new JsonResponse($response); + } +} diff --git a/src/Controller/Storefront/FrontendController.php b/src/Controller/Storefront/FrontendController.php new file mode 100644 index 0000000..9d8fe3a --- /dev/null +++ b/src/Controller/Storefront/FrontendController.php @@ -0,0 +1,174 @@ + ['storefront']])] +class FrontendController extends StorefrontController +{ + /** + * @var NovalnetHelper + */ + private $helper; + + /** + * @var RouterInterface + */ + private $router; + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * {@inheritdoc} + */ + public function __construct( + NovalnetHelper $helper, + RouterInterface $router, + TranslatorInterface $translator + ) { + $this->helper = $helper; + $this->router = $router; + $this->translator = $translator; + } + + #[Route(path: '/store/changePaymentData', name: 'frontend.novalnet.storeCustomerData', options: ['seo' => false], defaults: ['XmlHttpRequest' => true], methods: ['GET', 'POST'])] + public function storeCustomerData(Request $request, SalesChannelContext $context): JsonResponse + { + $data = $this->helper->unserializeData((string) $request->getContent()); + $paymentSettings = $this->helper->getNovalnetPaymentSettings($context->getSalesChannel()->getId()); + $parameters = $this->helper->getNovalnetRequestData(0, $data['parentOrderNumber'], $data, $context); + + if ((isset($data['booking_details']['do_redirect']) && ($data['booking_details']['do_redirect'] == 1)) || (isset($data['payment_details']['process_mode']) && $data['payment_details']['process_mode'] == 'redirect')) { + $parameters['transaction']['return_url'] = $parameters['transaction']['error_return_url'] = $this->generateAbsoluteUrl('frontend.novalnet.returnAction'); + } + + // Built custom parameters. + $parameters['transaction']['create_token'] = 1; + $parameters['custom']['input1'] = 'subscriptionId'; + $parameters['custom']['input2'] = 'paymentMethodId'; + $parameters['custom']['input3'] = 'change_payment'; + $parameters['custom']['inputval1'] = $data['aboId']; + $parameters['custom']['inputval2'] = $data['paymentMethodId']; + $parameters['custom']['inputval3'] = 1; + + $response = $this->helper->sendPostRequest($parameters, $this->helper->getActionEndpoint('payment'), $paymentSettings['NovalnetPayment.settings.accessKey']); + + if ($response['result']['status'] === 'FAILURE') { + return new JsonResponse(['success' => false, 'message' => $response['result']['status_text']]); + } else { + if (!empty($response['result']['redirect_url'])) { + $this->helper->setSession('iframeData', $data); + return new JsonResponse(['success' => true, 'redirect_url' => $response['result']['redirect_url']]); + } + $insertData = $this->insertTransactionData($response, $data, $context); + + // Upsert data into novalnet_transaction_details.repository + $this->helper->updateTransactionData($insertData, $context->getContext()); + + return new JsonResponse(['success' => true]); + } + } + + /** + * Generate the absolute URL. + * + * @param string $name + * @param array $parameter + * + * @return string + */ + public function generateAbsoluteUrl(string $name, array $parameter = []) + { + return $this->router->generate($name, $parameter, UrlGeneratorInterface::ABSOLUTE_URL); + } + + #[Route(path: '/novalnet/returnAction', name: 'frontend.novalnet.returnAction', options: ['seo' => false], methods: ['GET', 'POST'])] + public function returnAction(Request $request, SalesChannelContext $context): RedirectResponse + { + $response = $this->helper->fetchTransactionDetails($request, $context); + $localeCode = $this->helper->getLocaleCodeFromContext($context->getContext()); + if ($request->query->get('status') == 'SUCCESS') { + $data = $this->helper->getSession('iframeData'); + $insertData = $this->insertTransactionData($response, $data, $context); + // Upsert data into novalnet_transaction_details.repository + $this->helper->updateTransactionData($insertData, $context->getContext()); + + return $this->redirectToRoute( + 'frontend.novalnet.subscription.change.payment', + ['aboId' => $response['custom']['inputval1'], 'paymentMethodId' => $response['custom']['inputval2']] + ); + } else { + $this->addFlash(self::DANGER, $request->query->get('status_text') ?? $this->translator->trans('NovalnetPayment.text.changePaymentError', [], null, $localeCode)); + if (!empty($response['custom']['subscriptionId']) || !empty($response['custom']['inputval1'])) { + return $this->redirectToRoute( + 'frontend.novalnet.subscription.orders.detail', + ['aboId' => !empty($response['custom']['subscriptionId']) ? $response['custom']['subscriptionId'] : $response['custom']['inputval1']] + ); + } + return $this->redirectToRoute('frontend.novalnet.subscription.orders'); + } + } + + public function insertTransactionData(array $response, array $data, SalesChannelContext $context) : array + { + $paymentType = $response['transaction']['payment_type'] ?? 'NOVALNET_PAYMENT'; + + // insert novalnet transaction details + $insertData = [ + 'id' => Uuid::randomHex(), + 'paymentType' => $paymentType, + 'paidAmount' => 0, + 'tid' => $response['transaction']['tid'], + 'gatewayStatus' => $response['transaction']['status'], + 'amount' => $response['transaction']['amount'], + 'currency' => $response['transaction']['currency'], + 'orderNo' => $response['transaction']['order_no'], + 'customerNo' => !empty($response['customer']['customer_no']) ? $response['customer']['customer_no'] : '', + 'additionalDetails' => [ + 'payment_name' => $this->helper->getUpdatedPaymentName($paymentType, $this->helper->getLocaleCodeFromContext($context->getContext())), + 'change_payment' => true, + 'subscription' => $data + ] + ]; + + if (!empty($response['transaction']['payment_data']['token'])) { + $insertData['tokenInfo'] = $response['transaction']['payment_data']['token']; + } + + $insertData['additionalDetails'] = $this->helper->serializeData($insertData['additionalDetails']); + + return $insertData; + } +} diff --git a/src/Controller/Storefront/WebhookController.php b/src/Controller/Storefront/WebhookController.php new file mode 100644 index 0000000..6cad200 --- /dev/null +++ b/src/Controller/Storefront/WebhookController.php @@ -0,0 +1,1082 @@ + ['storefront']])] + +class WebhookController extends StorefrontController +{ + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var OrderTransactionStateHandler + */ + private $orderTransactionStateHandler; + + /** + * @var NovalnetHelper + */ + private $helper; + + /** + * @var NovalnetOrderTransactionHelper + */ + private $transactionHelper; + + /** + * @var string + */ + private $newLine = '/ '; + + /** + * @var string + */ + private $novalnetHostName = 'pay-nn.de'; + + /** + * @var string + */ + private $paymentMethodName; + + /** + * @var StateMachineRegistry + */ + private $stateMachineRegistry; + + /** + * @var array + */ + private $eventData; + + /** + * @var string + */ + private $eventType; + + /** + * @var string + */ + private $formattedAmount; + + /** + * @var OrderTransactionEntity + */ + private $orderTransaction; + + /** + * @var int + */ + private $parentTid; + + /** + * @var SalesChannelContext + */ + private $salesChannelContext; + + /** + * @var Context + */ + private $context; + + /** + * @var NovalnetPaymentTransactionEntity + */ + private $orderReference; + + /** + * @var int + */ + private $eventTid; + + /** + * @var array + */ + private $response; + + /** + * @var array + */ + private $paymentSettings; + + /** + * @var EntityRepository + */ + private $orderTransactionRepository; + + /** + * @var OrderEntity + */ + private $order; + + /** + * @var string + */ + private $locale; + + /** + * @var AbstractMailService + */ + private $mailService; + + /** + * @var array + */ + private $mandatoryParams = [ + 'event' => [ + 'type', + 'checksum', + 'tid' + ], + 'merchant' => [ + 'vendor', + 'project' + ], + 'transaction' => [ + 'tid', + 'payment_type', + 'status', + ], + 'result' => [ + 'status' + ], + ]; + + /** + * {@inheritdoc} + */ + public function __construct( + NovalnetHelper $helper, + NovalnetOrderTransactionHelper $transactionHelper, + TranslatorInterface $translator, + OrderTransactionStateHandler $orderTransactionStateHandler, + StateMachineRegistry $stateMachineRegistry, + EntityRepository $orderTransactionRepository, + AbstractMailService $mailService + ) { + $this->helper = $helper; + $this->transactionHelper = $transactionHelper; + $this->translator = $translator; + $this->orderTransactionStateHandler = $orderTransactionStateHandler; + $this->stateMachineRegistry = $stateMachineRegistry; + $this->orderTransactionRepository = $orderTransactionRepository; + $this->mailService = $mailService; + } + + #[Route(path: '/novalnet/callback', name: 'api.action.novalnetpayment.status-action', defaults: ['csrf_protected' => false], methods: ['GET', 'POST'])] + public function statusAction(Request $request, SalesChannelContext $salesChannelContext): Response + { + $this->salesChannelContext = $salesChannelContext; + $this->context = $this->salesChannelContext->getContext(); + $this->eventData = $this->helper->unserializeData((string) $request->getContent()); + if (!$this->eventData) { + $this->response = ['message' => "Received data is not in the JSON format"]; + return $this->debugMessage(); + } + + if (! empty($this->eventData ['transaction'] ['order_no'])) { + $orderNumber = $this->eventData ['transaction'] ['order_no']; + $order = $this->transactionHelper->getOrderEntity($orderNumber, $this->context); + $this->paymentSettings = $order != null ? $this->helper->getNovalnetPaymentSettings($order->getSalesChannel()->getId()) : $this->helper->getNovalnetPaymentSettings($this->salesChannelContext->getSalesChannel()->getId()); + } else { + $this->paymentSettings = $this->helper->getNovalnetPaymentSettings($this->salesChannelContext->getSalesChannel()->getId()); + } + + if (!$this->authenticateRequestIp() || !$this->validateEventData() || !$this->validateChecksum()) { + return $this->debugMessage(); + } + + $this->eventType = $this->eventData ['event'] ['type']; + $this->eventTid = $this->eventData ['event'] ['tid']; + $this->parentTid = $this->eventTid; + + if (! empty($this->eventData ['event'] ['parent_tid'])) { + $this->parentTid = $this->eventData ['event'] ['parent_tid']; + } + + if (! empty($this->eventData ['instalment']['cycle_amount'])) { + $this->formattedAmount = $this->helper->amountInBiggerCurrencyUnit((int) $this->eventData ['instalment']['cycle_amount'], $this->eventData ['transaction'] ['currency'], $this->context); + } elseif (!empty($this->eventData ['transaction'] ['amount'])) { + $this->formattedAmount = $this->helper->amountInBiggerCurrencyUnit((int) $this->eventData ['transaction'] ['amount'], $this->eventData ['transaction'] ['currency'], $this->context); + } + + // Get order reference. + if (!$this->getOrderReference()) { + return $this->debugMessage(); + } + + $this->eventData ['transaction'] ['currency'] = isset($this->eventData ['transaction'] ['currency']) ? $this->eventData ['transaction'] ['currency'] : $this->orderReference->getCurrency(); + + $this->response ['message'] = ''; + + if ($this->helper->isSuccessStatus($this->eventData)) { + switch ($this->eventType) { + case "PAYMENT": + $this->response ['message'] .= 'Novalnet Callback executed. The Transaction ID already existed'; + break; + + case "CREDIT": + $callbackComments = $this->creditProcess(); + break; + + case "TRANSACTION_CAPTURE": + case "TRANSACTION_CANCEL": + $callbackComments = $this->transactionCaptureVoid(); + break; + + case "INSTALMENT": + $callbackComments = $this->instalmentProcess(); + break; + + case "CHARGEBACK": + case "RETURN_DEBIT": + case "REVERSAL": + $callbackComments = $this->chargebackProcess(); + break; + + case "PAYMENT_REMINDER_1": + case "PAYMENT_REMINDER_2": + $callbackComments = $this->paymentReminderProcess(); + break; + + case "SUBMISSION_TO_COLLECTION_AGENCY": + $callbackComments = $this->collectionProcess(); + break; + + case "TRANSACTION_UPDATE": + $callbackComments = $this->transactionUpdate(); + break; + + case "TRANSACTION_REFUND": + $callbackComments = $this->transactionrefund(); + break; + + case "INSTALMENT_CANCEL": + $callbackComments = $this->instalmentCancelProcess(); + break; + + default: + $this->response ['message'] .= "The webhook notification has been received for the unhandled EVENT type($this->eventType)"; + } + } else { + $this->response ['message'] .= 'The Payment has been received'; + } + if (!empty($callbackComments)) { + $this->response['message'] .= $callbackComments; + $this->sendNotificationEmail(); + } + return $this->debugMessage(); + } + + /** + * Authenticate server request + * + * @return bool + */ + public function authenticateRequestIp() : bool + { + // Host based validation. + if (! empty($this->novalnetHostName)) { + + // Authenticating the server request based on IP. + $requestReceivedIp = $this->helper->checkWebhookIp(gethostbyname($this->novalnetHostName)); + $deactivateIp = $this->paymentSettings ['NovalnetPayment.settings.deactivateIp']; + if (empty($requestReceivedIp) && empty($deactivateIp)) { + $this->response = ['message' => "Unauthorised access from the IP $requestReceivedIp"]; + return false; + } + } else { + $this->response = [ 'message' => 'Unauthorised access from the IP. Novalnet Host name is empty' ]; + return false; + } + + return true; + } + + /** + * Validate EventData + * + * @return bool + */ + + public function validateEventData() : bool + { + if (! empty($this->eventData ['custom'] ['shop_invoked'])) { + $this->response = [ 'message' => 'Process already handled in the shop.' ]; + return false; + } + + foreach ($this->mandatoryParams as $category => $parameters) { + if (empty($this->eventData [ $category ])) { + // Could be a possible manipulation in the notification data. + $this->response = [ 'message' => "Required parameter category($category) not received" ]; + } elseif (! empty($parameters)) { + foreach ($parameters as $parameter) { + if (!isset($this->eventData [ $category ] [ $parameter ])) { + // Could be a possible manipulation in the notification data. + $this->response = [ 'message' => "Required parameter($parameter) in the category($category) not received" ]; + return false; + } elseif (in_array($parameter, [ 'tid', 'parent_tid' ], true) && ! preg_match('/^\d{17}$/', (string) $this->eventData [ $category ] [ $parameter ])) { + $this->response = [ 'message' => "Invalid TID received in the category($category) not received $parameter" ]; + return false; + } + } + } + } + return true; + } + + /** + * Validate checksum + * + * @return bool + */ + + public function validateChecksum() : bool + { + $tokenString = $this->eventData ['event'] ['tid'] . $this->eventData ['event'] ['type']. $this->eventData ['result'] ['status']; + + if (isset($this->eventData ['transaction'] ['amount'])) { + $tokenString .= $this->eventData ['transaction'] ['amount']; + } + if (isset($this->eventData ['transaction'] ['currency'])) { + $tokenString .= $this->eventData ['transaction'] ['currency']; + } + + $paymentAccessKey = $this->paymentSettings['NovalnetPayment.settings.accessKey']; + if (! empty($paymentAccessKey)) { + $tokenString .= strrev($paymentAccessKey); + } + $generatedChecksum = hash('sha256', $tokenString); + + if ($generatedChecksum !== $this->eventData ['event'] ['checksum']) { + $this->response = [ 'message' => 'While notifying some data has been changed. The hash check failed' ]; + return false; + } + + return true; + } + + /** + * Print the Webhook messages. + * + * @return Response + */ + public function debugMessage() : Response + { + return new Response($this->helper->serializeData($this->response)); + } + + /** + * Get order reference from the novalnet_transaction_detail table on shop database. + * + * @return bool + */ + + public function getOrderReference() :bool + { + $orderNumber = ''; + $paymentMethod = ''; + + if (! empty($this->eventData ['transaction'] ['order_no']) || ! empty($this->parentTid)) { + if (! empty($this->eventData ['transaction'] ['order_no'])) { + $orderNumber = $this->eventData ['transaction'] ['order_no']; + } + + $this->order = $this->transactionHelper->getOrderEntity($orderNumber, $this->context); + + if ($this->order == null) + { + $this->response = [ 'message' => 'Order Reference not exist in Database!']; + return false; + } + + $this->orderTransaction = $this->order->getTransactions()->last(); + $this->locale = !empty($this->order->getLanguageId()) ? $this->helper->getLocaleCodeFromContext($this->context, true, $this->order->getLanguageId()) : $this->helper->getLocaleCodeFromContext($this->context, true); + + if (!is_null($this->orderTransaction)) { + $paymentMethod = $this->orderTransaction->getPaymentMethod(); + if (!empty($paymentMethod) && !empty($paymentMethod->getHandlerIdentifier())) { + $paymentHandlerIdentifier = (explode("\\",$paymentMethod->getHandlerIdentifier())); + $this->paymentMethodName = end($paymentHandlerIdentifier); + } + } + + if ($this->paymentMethodName != 'NovalnetPayment' && empty($this->eventData ['custom']['change_payment'])) { + $this->response = ['message' => 'Order Reference not exist in Database!']; + return false; + } + + $this->paymentSettings = $this->helper->getNovalnetPaymentSettings($this->order->getSalesChannel()->getId()); + $this->eventData['transaction']['currency'] = !empty($this->eventData['transaction']['currency']) ? $this->eventData['transaction']['currency'] : $this->order->getCurrency()->getIsoCode(); + $this->eventData['customer']['customer_no'] = !empty($this->eventData['customer']['customer_no']) ? $this->eventData['customer']['customer_no'] : $this->order->getOrderCustomer()->getCustomerNumber(); + + if (isset($this->eventData ['custom']['change_payment']) && !empty($this->eventData ['custom']['change_payment'])) { + if ($this->helper->isSuccessStatus($this->eventData)) { + $orderData = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $this->context, (string) $this->eventData ['transaction']['tid'], true); + if (empty($orderData)) { + $this->transactionHelper->updateChangePayment($this->eventData, $this->order->getId(), $this->context); + $this->response = [ 'message' => 'Communication failure has been handled successfully. The transaction details has been updated' ]; + } else { + $this->response = [ 'message' => 'Novalnet Callback executed. The Transaction ID already existed' ]; + } + } else { + $this->response = [ 'message' => 'The payment failure has been handled in the shop.' ]; + } + return false; + } + + $this->orderReference = $this->transactionHelper->fetchNovalnetTransactionData((string) $orderNumber, $this->context, (string) $this->parentTid); + + if ($this->orderReference == null) { + if ($this->helper->isSuccessStatus($this->eventData)) { + if ($this->eventData ['transaction'] ['payment_type'] === 'ONLINE_TRANSFER_CREDIT' && !empty($this->parentTid)) { + $response = $this->helper->sendPostRequest(['transaction' => ['tid' => $this->parentTid]], $this->helper->getActionEndpoint('transaction_details'), $this->paymentSettings['NovalnetPayment.settings.accessKey']); + if (!empty($response['transaction'] ['payment_type'])) { + $this->eventData['transaction'] ['payment_type'] = $response['transaction'] ['payment_type']; + } + $this->updateInitialPayment($paymentMethod); + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.creditMessage', [], null, $this->locale), $this->parentTid, $this->formattedAmount, date('d/m/Y H:i:s'), $this->eventTid); + $this->transactionHelper->postProcess($this->orderTransaction, $this->context, $callbackComments); + } else { + $this->updateInitialPayment($paymentMethod); + } + } else { + $customFields = $this->orderTransaction->getCustomFields(); + if (empty($customFields['novalnet_comments']) || !preg_match('/'.$this->eventData ['transaction']['tid'].'/', $customFields['novalnet_comments'])) { + $this->updateInitialPayment($paymentMethod); + } else { + $this->response = [ 'message' => 'Novalnet Callback executed. The Transaction ID already existed' ]; + } + } + return false; + } + + if ($this->eventType == "PAYMENT" && $this->helper->isSuccessStatus($this->eventData) && !empty($this->eventData['transaction']['amount']) && $this->orderReference->getAmount() == 0 && $this->eventData['transaction']['tid'] != $this->orderReference->getTid()) { + $bookAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit($this->eventData['transaction'] ['amount'], $this->eventData['transaction'] ['currency'], $this->context); + $message = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.bookedComment', [], null, $this->locale), $bookAmountInBiggerUnit, $this->eventData['transaction'] ['tid']); + + $this->transactionHelper->postProcess($this->orderTransaction, $this->context, $message, [ + 'id' => $this->orderReference->getId(), + 'tid' => $this->eventData['transaction']['tid'], + 'amount' => $this->eventData['transaction']['amount'], + 'paidAmount' => $this->eventData['transaction']['amount'], + 'gatewayStatus' => $this->eventData['transaction']['status'], + ]); + + try { + if (!empty($this->paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $this->paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->transactionHelper->managePaymentStatus($completeStatus, $this->orderTransaction->getId(), $this->context); + } else { + $this->orderTransactionStateHandler->paid($this->orderTransaction->getId(), $this->context); + } + } catch (IllegalTransitionException $exception) { + } + + $this->response = ['message' => $message]; + return false; + } + } + + + return true; + } + + /** + * Handle communication failure + * + * @param PaymentMethodEntity $paymentMethod + */ + public function updateInitialPayment(PaymentMethodEntity $paymentMethod): void + { + $handlerIdentifier = $paymentMethod->getHandlerIdentifier(); + + if (strpos($handlerIdentifier, "\NovalnetPayment")) { + $handlerIdentifier = 'Novalnet\NovalnetPayment\Service\NovalnetPayment'; + } + + $paymentMethodInstance = new $handlerIdentifier( + $this->helper, + $this->transactionHelper, + $this->orderTransactionStateHandler, + $this->orderTransactionRepository + ); + + $paymentTransaction = new AsyncPaymentTransactionStruct($this->orderTransaction, $this->order, $this->generateUrl('frontend.checkout.cart.page')); + + if (method_exists($paymentMethodInstance, 'checkTransactionStatus')) { + $this->transactionHelper->updateSubscriptionData($this->eventData['transaction']['status'], $this->orderTransaction->getPaymentMethodId(), $this->order->getId()); + $paymentMethodInstance->checkTransactionStatus($this->orderTransaction, $this->eventData, $this->salesChannelContext, $paymentTransaction, '1'); + if ( !empty($this->orderTransaction->getCustomFields()['novalnet_comments'])) { + $this->response = [ 'message' => 'The transaction details has been updated successfully' ]; + } else { + $this->response = [ 'message' => 'Communication failure has been handled successfully. The transaction details has been updated' ]; + } + } else { + $this->response = [ 'message' => 'Payment not found in the order' ]; + } + } + + /** + * Handle payment credit process + * + * @return string + */ + private function creditProcess(): string + { + $upsertData = []; + if (!empty($this->eventData['transaction']['amount'])) { + if ((int) $this->orderReference->getPaidAmount() < (int) $this->orderReference->getAmount() && in_array($this->eventData['transaction']['payment_type'], ['INVOICE_CREDIT', 'CASHPAYMENT_CREDIT', 'MULTIBANCO_CREDIT', 'ONLINE_TRANSFER_CREDIT', 'DEBT_COLLECTION_DE'])) { + $paidAmount = (int) $this->orderReference->getPaidAmount() + (int) $this->eventData['transaction']['amount']; + $totalAmount = (int) $this->orderReference->getAmount() - (int) $this->orderReference->getRefundedAmount(); + + $upsertData['id'] = $this->orderReference->getId(); + $upsertData['gatewayStatus'] = $this->eventData['transaction']['status']; + $upsertData['paidAmount'] = $paidAmount; + + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.creditMessage', [], null, $this->locale), $this->parentTid, $this->formattedAmount, date('d/m/Y H:i:s'), $this->eventTid); + + $this->transactionHelper->postProcess($this->orderTransaction, $this->context, $callbackComments, $upsertData); + + if (($paidAmount >= $totalAmount)) { + try { + if (version_compare($this->helper->getShopVersion(), '6.4.7.0', '>=')) { + $this->orderTransactionStateHandler->process($this->orderTransaction->getId(), $this->context); + } + if (!empty($this->paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $this->paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->transactionHelper->managePaymentStatus($completeStatus, $this->orderTransaction->getId(), $this->context); + } else { + $this->orderTransactionStateHandler->paid($this->orderTransaction->getId(), $this->context); + } + } catch (IllegalTransitionException $exception) { + } + } elseif ($paidAmount !=0 && $paidAmount < $totalAmount) { + $this->orderTransactionStateHandler->payPartially($this->orderTransaction->getId(), $this->context); + } + return $callbackComments; + } else { + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.creditMessage', [], null, $this->locale), $this->parentTid, $this->formattedAmount, date('d/m/Y H:i:s'), $this->eventTid); + $this->transactionHelper->postProcess($this->orderTransaction, $this->context, $callbackComments, []); + return $callbackComments; + } + } else { + $callbackComments = 'Required parameter (amount) in the category (transaction) not received'; + return $callbackComments; + } + } + + /** + * Handle transaction Capture process + * + * @return string + */ + private function transactionCaptureVoid(): string + { + $callbackComments = ''; + $appendComments = true; + $upsertData =[]; + if (in_array($this->orderReference->getGatewayStatus(), ['ON_HOLD', 'PENDING'])) { + $upsertData['id'] = $this->orderReference->getId(); + + $transactionStatus = $this->eventData['transaction']['status']; + $salesChannelContext = $this->salesChannelContext->getContext(); + $additionalDetails = $this->orderReference->getAdditionalDetails(); + $transactionAdditionDetails = $this->helper->unserializeData($additionalDetails); + $paymentType = $this->orderReference->getPaymentType(); + $orderPaymentType = $this->helper->getUpdatedPaymentType($paymentType); + $this->eventData['transaction']['amount'] = !empty($this->eventData['transaction']['amount']) ? $this->eventData['transaction']['amount'] : $this->orderReference->getAmount(); + + if ($this->eventType === 'TRANSACTION_CAPTURE') { + if ($orderPaymentType === 'INVOICE') { + $transactionStatus = 'PENDING'; + } + if (in_array($transactionStatus, ['CONFIRMED', 'PENDING'])) { + if ($transactionStatus == 'CONFIRMED') { + $upsertData['paidAmount'] = $this->orderReference->getAmount(); + if (in_array($this->eventData['transaction']['payment_type'], ['INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $upsertData['additionalDetails'] = $transactionAdditionDetails; + $this->eventData['transaction']['amount'] = $this->orderReference->getAmount(); + $upsertData['additionalDetails']['InstalmentDetails'] = $this->transactionHelper->getInstalmentInformation($this->eventData, $this->locale); + $upsertData['additionalDetails'] = $this->helper->serializeData($upsertData['additionalDetails']); + } + } + } + } + + if (!empty($additionalDetails) && !empty($paymentType) && in_array($orderPaymentType, ['INVOICE', 'GUARANTEED_INVOICE', 'PREPAYMENT', 'INSTALMENT_INVOICE'])) { + $appendComments = false; + if (!empty($transactionAdditionDetails['bankDetails'])) { + $bankDetails = $transactionAdditionDetails['bankDetails']; + } elseif (!empty($transactionAdditionDetails['account_holder'])) { + $bankDetails = $transactionAdditionDetails; + } + + if (!empty($bankDetails)) { + $this->eventData['transaction']['bank_details'] = $bankDetails; + } + $callbackComments .= $this->helper->formBankDetails($this->eventData, $salesChannelContext, $this->order->getLanguageId()) . $this->newLine; + } + + if (!empty($paymentType) && in_array($orderPaymentType, ['GUARANTEED_DIRECT_DEBIT_SEPA', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $appendComments = false; + $callbackComments .= $this->helper->formBankDetails($this->eventData, $salesChannelContext, $this->order->getLanguageId()) . $this->newLine; + } + + $transactionMsg = ($this->eventType === 'TRANSACTION_CAPTURE') ? 'NovalnetPayment.text.confirmMessage' : 'NovalnetPayment.text.faliureMessage'; + $callbackComments .= $this->newLine . sprintf($this->translator->trans((string)$transactionMsg, [], null, $this->locale), date('d/m/Y H:i:s')); + $upsertData['gatewayStatus'] = $transactionStatus; + $this->transactionHelper->postProcess($this->orderTransaction, $salesChannelContext, $callbackComments, $upsertData, $appendComments); + + if (in_array($this->eventData['transaction']['payment_type'], ['INVOICE', 'GUARANTEED_INVOICE', 'INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $this->transactionHelper->prepareMailContent($this->order, $this->salesChannelContext, $callbackComments); + } + try { + if ($transactionStatus == 'CONFIRMED') { + if (!empty($this->paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $this->paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->transactionHelper->managePaymentStatus($completeStatus, $this->orderTransaction->getId(), $salesChannelContext); + } else { + $this->orderTransactionStateHandler->paid($this->orderTransaction->getId(), $salesChannelContext); + } + } elseif ($transactionStatus == 'PENDING') { + $this->transactionHelper->managePaymentStatus('process', $this->orderTransaction->getId(), $salesChannelContext); + } elseif ($this->eventType === 'TRANSACTION_CANCEL') { + $this->orderTransactionStateHandler->cancel($this->orderTransaction->getId(), $salesChannelContext); + } + } catch (IllegalTransitionException $exception) { + } + } else { + $this->response ['message'] = 'Order already processed.'; + } + + return $callbackComments; + } + + + + /** + * Handle payment INSTALMENT process + * + * @return string + */ + private function instalmentProcess(): string + { + $instalmentData = $this->eventData['instalment']; + $comments = ''; + if (!empty($instalmentData['cycle_amount']) && !empty($instalmentData['cycles_executed']) && isset($instalmentData['pending_cycles'])) { + if (in_array($this->eventData['transaction']['payment_type'], ['INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA']) && $this->orderReference->getGatewayStatus() == 'CONFIRMED') { + $comments = $this->helper->formBankDetails($this->eventData, $this->salesChannelContext->getContext(), $this->order->getLanguageId()); + } + + $comments .= $this->newLine . $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.instalmentPrepaidMessage', [], null, $this->locale), $this->parentTid, $this->formattedAmount, $this->eventTid).$this->newLine; + $upsertData['id'] = $this->orderReference->getId(); + $upsertData['additionalDetails'] = $this->updateInstalmentInfo(); + + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $comments, $upsertData, false); + $this->transactionHelper->prepareMailContent($this->order, $this->salesChannelContext, $comments, true); + + return $comments; + } else { + $comments = 'Required parameter in the category (instalment) not received'; + return $comments; + } + } + + /** + * Form the instalment data into serialize + * + * @return string + */ + private function updateInstalmentInfo(): string + { + $configurationDetails = $this->helper->unserializeData($this->orderReference->getAdditionalDetails()); + $instalmentData = $this->eventData['instalment']; + $configurationDetails['InstalmentDetails'][$instalmentData['cycles_executed']] = [ + 'amount' => $instalmentData['cycle_amount'], + 'cycleDate' => !empty($instalmentData['next_cycle_date']) ? date('Y-m-d', strtotime($instalmentData['next_cycle_date'])) : '', + 'cycleExecuted' => $instalmentData['cycles_executed'], + 'dueCycles' => $instalmentData['pending_cycles'], + 'paidDate' => date('Y-m-d'), + 'status' => $this->translator->trans('NovalnetPayment.text.paidMsg', [], null, $this->locale), + 'reference' => (string) $this->eventData['transaction']['tid'], + 'refundAmount' => 0, + ]; + return $this->helper->serializeData($configurationDetails); + } + + /** + * Handle payment CHARGEBACK/RETURN_DEBIT/REVERSAL process + * + * @return string + */ + private function chargebackProcess(): string + { + $callbackComments = ''; + if (! empty($this->eventData ['transaction'] ['amount'])) { + if ($this->orderReference->getGatewayStatus()=='CONFIRMED') { + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.chargebackComments', [], null, $this->locale), $this->parentTid, $this->formattedAmount, date('d/m/Y H:i:s'), $this->eventTid); + + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $callbackComments); + + if ($this->eventData ['transaction'] ['amount'] >= $this->orderReference->getAmount()) { + try { + $this->stateMachineRegistry->transition( + new Transition( + OrderTransactionDefinition::ENTITY_NAME, + $this->orderTransaction->getId(), + StateMachineTransitionActions::ACTION_CHARGEBACK, + 'stateId' + ), + $this->salesChannelContext->getContext() + ); + } catch (IllegalTransitionException $exception) { + } + } + } + } else { + $callbackComments = 'Required parameter (amount) in the category (transaction) not received'; + return $callbackComments; + } + return $callbackComments; + } + + /** + * Handle payment reminder process + * + * @return string + */ + private function paymentReminderProcess(): string + { + $callbackComments = ''; + + if (in_array($this->orderReference->getGatewayStatus(), ['CONFIRMED', 'PENDING'])) { + $reminderCount = preg_replace('/[^0-9]/', '', $this->eventType); + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.paymentReminder', [], null, $this->locale), $reminderCount); + + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $callbackComments); + } + return $callbackComments; + } + + /** + * Handle collection process + * + * @return string + */ + private function collectionProcess(): string + { + $callbackComments = ''; + + if (in_array($this->orderReference->getGatewayStatus(), ['CONFIRMED', 'PENDING'])) { + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.collectionSubmission', [], null, $this->locale), $this->eventData['collection']['reference']); + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $callbackComments); + } + return $callbackComments; + } + + /** + * Handle transaction update + * + * @return string + */ + + private function transactionUpdate(): string + { + $transactionStatus = $this->eventData['transaction']['status']; + $salesChannelContext = $this->salesChannelContext->getContext(); + $transactionAdditionDetails = $this->helper->unserializeData($this->orderReference->getAdditionalDetails()); + + $upsertData = [ + 'id' => $this->orderReference->getId(), + 'gatewayStatus' => $transactionStatus, + ]; + + $callbackComments = ''; + $appendComments = true; + $paymentType = $this->eventData['transaction']['payment_type']; + + if (in_array($transactionStatus, ['CONFIRMED', 'PENDING', 'ON_HOLD', 'DEACTIVATED' ])) { + if (in_array($this->eventData['transaction']['update_type'], ['DUE_DATE', 'AMOUNT_DUE_DATE'])) { + $this->eventData['transaction']['amount'] = isset($this->eventData['transaction']['amount']) ? $this->eventData['transaction']['amount'] : $this->orderReference->getAmount(); + $upsertData['amount'] = $this->eventData['transaction']['amount']; + $dueDate = date('d/m/Y', strtotime($this->eventData['transaction']['due_date'])); + + if ($paymentType === 'CASHPAYMENT') { + $callbackComments .= $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.cashDueDateComments', [], null, $this->locale), $this->formattedAmount, $dueDate); + } else { + $callbackComments .= $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.dueDateComments', [], null, $this->locale), $this->formattedAmount, $dueDate); + } + } elseif ($this->eventData['transaction']['update_type'] == 'STATUS') { + $this->eventData['transaction']['amount'] = isset($this->eventData['transaction']['amount']) ? $this->eventData['transaction']['amount'] : $this->orderReference->getAmount(); + if (in_array($paymentType, ['GUARANTEED_INVOICE', 'INSTALMENT_INVOICE', 'INVOICE', 'GUARANTEED_DIRECT_DEBIT_SEPA', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $appendComments = false; + if (!empty($this->orderReference->getAdditionalDetails())) { + if (!empty($transactionAdditionDetails['bankDetails'])) { + $bankDetails = $transactionAdditionDetails['bankDetails']; + } elseif (!empty($transactionAdditionDetails['account_holder'])) { + $bankDetails = $transactionAdditionDetails; + } + if (!empty($bankDetails) && ($transactionStatus != 'DEACTIVATED')) { + $this->eventData['transaction']['bank_details'] = $bankDetails; + } + } + $callbackComments = $this->helper->formBankDetails($this->eventData, $salesChannelContext, $this->order->getLanguageId()); + } + + if ($transactionStatus === 'DEACTIVATED') { + $callbackComments .= $this->newLine . $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.faliureMessage', [], null, $this->locale), date('d/m/Y H:i:s')); + } elseif (in_array($this->orderReference->getGatewayStatus(), ['PENDING','ON_HOLD'])) { + $upsertData['amount'] = $this->eventData['transaction']['amount']; + + if ($transactionStatus === 'ON_HOLD') { + $callbackComments .= sprintf($this->newLine . $this->translator->trans('NovalnetPayment.text.updateOnholdComments', [], null, $this->locale), $this->eventTid, date('d/m/Y H:i:s')); + } elseif ($transactionStatus === 'CONFIRMED') { + $callbackComments .= $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.updateComments1', [], null, $this->locale), $this->eventTid, $this->formattedAmount, date('d/m/Y H:i:s')); + + if (in_array($paymentType, ['INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $upsertData['additionalDetails'] = $transactionAdditionDetails; + $this->eventData['transaction']['amount'] = $this->orderReference->getAmount(); + $upsertData['additionalDetails']['InstalmentDetails'] = $this->transactionHelper->getInstalmentInformation($this->eventData, $this->locale); + $upsertData['additionalDetails'] = $this->helper->serializeData($upsertData['additionalDetails']); + } elseif (in_array($paymentType, ['PAYPAL', 'PRZELEWY24', 'TRUSTLY'])) { + $callbackComments = sprintf($this->newLine. $this->translator->trans('NovalnetPayment.text.redirectUpdateComment', [], null, $this->locale), $this->eventTid, date('d/m/Y H:i:s')); + } + + $upsertData['paidAmount'] = $this->eventData['transaction']['amount']; + } + } + } else { + if (!empty($this->eventData['transaction']['amount'])) { + $upsertData['amount'] = $this->eventData['transaction']['amount']; + } + $callbackComments .= sprintf($this->newLine . $this->translator->trans('NovalnetPayment.text.updateComments1', [], null, $this->locale), $this->eventTid, $this->formattedAmount, date('d/m/Y H:i:s')); + } + } + + $this->transactionHelper->postProcess($this->orderTransaction, $salesChannelContext, $callbackComments, $upsertData, $appendComments); + + $orderTransactionId = $this->orderTransaction->getId(); + + try { + if ($transactionStatus === 'DEACTIVATED') { + $this->orderTransactionStateHandler->cancel($orderTransactionId, $salesChannelContext); + } elseif ($transactionStatus === 'CONFIRMED' && $this->eventData['transaction']['amount'] == 0 && !in_array($paymentType, ['PREPAYMENT'])) { + $this->orderTransactionStateHandler->authorize($orderTransactionId, $salesChannelContext); + } elseif ($transactionStatus=== 'CONFIRMED') { + if (!empty($this->paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $this->paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->transactionHelper->managePaymentStatus($completeStatus, $orderTransactionId, $salesChannelContext); + } else { + $this->orderTransactionStateHandler->paid($orderTransactionId, $this->context); + } + } elseif (($transactionStatus === 'ON_HOLD')) { + if (!empty($this->paymentSettings['NovalnetPayment.settings.onHoldStatus'])) { + $onHoldStatus = $this->paymentSettings['NovalnetPayment.settings.onHoldStatus']; + $this->transactionHelper->managePaymentStatus($onHoldStatus, $orderTransactionId, $salesChannelContext); + } else { + $this->orderTransactionStateHandler->authorize($orderTransactionId, $salesChannelContext); + } + } elseif (($transactionStatus === 'PENDING')) { + $this->transactionHelper->managePaymentStatus('process', $orderTransactionId, $salesChannelContext); + } + } catch (IllegalTransitionException $exception) { + } + + if (in_array($paymentType, ['INVOICE', 'GUARANTEED_INVOICE', 'INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA']) && in_array($transactionStatus, ['CONFIRMED', 'PENDING', 'ON_HOLD'])) { + $this->transactionHelper->prepareMailContent($this->order, $this->salesChannelContext, $callbackComments); + } + + return $callbackComments; + } + + /** + * Transaction refund + * + * @return string + */ + + private function transactionrefund(): string + { + $callbackComments = ''; + + if (!empty($this->eventData['transaction']['refund']['amount'])) { + $refundAmount = $this->eventData['transaction']['refund']['amount']; + } else { + $refundAmount = (int) $this->orderReference->getAmount() - (int) $this->orderReference->getRefundedAmount(); + } + + if (!empty($refundAmount)) { + $refundedAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit((int) $refundAmount, $this->eventData ['transaction'] ['currency'], $this->salesChannelContext->getContext()); + + $callbackComments = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.refundComment', [], null, $this->locale), $this->parentTid, $refundedAmountInBiggerUnit); + + if (!empty($this->eventData['transaction']['refund']['tid'])) { + $callbackComments .= sprintf($this->translator->trans('NovalnetPayment.text.refundCommentForNewTid', [], null, $this->locale), $this->eventData['transaction']['refund']['tid']); + } + $additionalDetails = $this->helper->unserializeData($this->orderReference->getAdditionalDetails()); + + if (in_array($this->helper->getUpdatedPaymentType($this->orderReference->getpaymentType()), ['INSTALMENT_INVOICE', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $additionalDetails['InstalmentDetails'] = $this->transactionHelper->updateInstalmentCycle($additionalDetails['InstalmentDetails'], (int)$refundAmount, (string) $this->parentTid, $this->locale); + } + + $totalRefundedAmount = (int) $this->orderReference->getRefundedAmount() + (int) $refundAmount; + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $callbackComments, [ + 'id' => $this->orderReference->getId(), + 'refundedAmount' => $totalRefundedAmount, + 'gatewayStatus' => $this->eventData['transaction']['status'], + 'additionalDetails' => !empty($additionalDetails) ? $this->helper->serializeData($additionalDetails) : null, + + ]); + + if ($totalRefundedAmount >= $this->orderReference->getAmount()) { + try { + $this->orderTransactionStateHandler->refund($this->orderTransaction->getId(), $this->salesChannelContext->getContext()); + } catch (IllegalTransitionException $exception) { + $this->orderTransactionStateHandler->cancel($this->orderTransaction->getId(), $this->salesChannelContext->getContext()); + } + } + } + return $callbackComments; + } + + + /** + * instalment cancel + * + * @return string + */ + + private function instalmentCancelProcess(): string + { + $callbackComments = ''; + + $additionalDetails = $this->helper->unserializeData($this->orderReference->getAdditionalDetails()); + if (!empty($this->eventData['instalment']['cancel_type'])) { + if (isset($this->eventData['transaction']['refund'])) { + $refundedAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit((int) $this->eventData['transaction']['refund']['amount'], $this->orderReference->getCurrency(), $this->salesChannelContext->getContext()); + $callbackComments .= $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.instalmentRefundComment', [], null, $this->locale), $this->parentTid, date('Y-m-d H:i:s'), $refundedAmountInBiggerUnit); + $totalRefundedAmount = $this->orderReference->getAmount(); + } else { + $callbackComments .= $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.instalmentRemainRefundComment', [], null, $this->locale), $this->parentTid, date('Y-m-d H:i:s')); + $totalRefundedAmount = 0; + foreach ($additionalDetails['InstalmentDetails'] as $instalment) { + $totalRefundedAmount += empty($instalment['reference']) ? $instalment['amount'] : 0; + } + } + + $additionalDetails['InstalmentDetails'] = $this->transactionHelper->updateInstalmentCancel($additionalDetails['InstalmentDetails'], $this->eventData['instalment']['cancel_type'], $this->locale); + $additionalDetails['cancelType'] = $this->eventData['instalment']['cancel_type']; + + $this->transactionHelper->postProcess($this->orderTransaction, $this->salesChannelContext->getContext(), $callbackComments, [ + 'id' => $this->orderReference->getId(), + 'refundedAmount' => $totalRefundedAmount, + 'gatewayStatus' => $this->eventData['transaction']['status'], + 'additionalDetails' => $this->helper->serializeData($additionalDetails), + ]); + + if ($totalRefundedAmount >= $this->orderReference->getAmount()) { + try { + $this->orderTransactionStateHandler->cancel($this->orderTransaction->getId(), $this->salesChannelContext->getContext()); + } catch (IllegalTransitionException $exception) { + } + } + + return $callbackComments; + } else { + $callbackComments = 'Required parameter (cancel type) in the category (instalment) not received'; + return $callbackComments; + } + } + + /** + * Send notify email after callback process. + * + * @return void + */ + public function sendNotificationEmail(): void + { + if (!empty($this->paymentSettings['NovalnetPayment.settings.mailTo']) && ! empty($this->response['message'])) { + $toEmail = explode(',', $this->paymentSettings['NovalnetPayment.settings.mailTo']); + $data = new DataBag(); + $mailSubject = 'Novalnet Callback Script Access Report - '; + if (! empty($this->eventData ['transaction']['order_no'])) { + $mailSubject .= 'Order No : ' . $this->eventData ['transaction']['order_no']; + } + $mailSubject .= ' in the ' . (!empty($this->salesChannelContext->getSalesChannel()->getTranslated()) ? $this->salesChannelContext->getSalesChannel()->getTranslated()['name'] : $this->salesChannelContext->getSalesChannel()->getName()); + + $senderEmail = []; + foreach ($toEmail as $email) { + if ($this->helper->isValidEmail($email)) { + $senderEmail = array_merge($senderEmail, [$email => $email]); + } + } + $data->set( + 'recipients', + $senderEmail + ); + $data->set('senderName', 'Novalnet'); + $data->set('salesChannelId', null); + $data->set('contentHtml', str_replace('/ ', '
', $this->response['message'])); + $data->set('contentPlain', $this->response['message']); + $data->set('subject', $mailSubject); + + try { + $this->mailService->send( + $data->all(), + $this->salesChannelContext->getContext(), + [] + ); + } catch (\Exception $e) { + throw($e); + } + } + } + + +} diff --git a/src/Helper/NovalnetHelper.php b/src/Helper/NovalnetHelper.php new file mode 100644 index 0000000..9cd6c2a --- /dev/null +++ b/src/Helper/NovalnetHelper.php @@ -0,0 +1,1161 @@ +translator = $translator; + $this->container = $container; + $this->systemConfigService = $systemConfigService; + $this->requestStack = $requestStack; + $this->languageRepository = $this->container->get('language.repository'); + $this->currencyFormatter = $currencyFormatter; + $this->shopVersion = $shopVersion; + } + + /** + * Request to payment gateway action. + * + * @param array $parameters + * @param string $url + * @param string $accessKey + * + * @return array + */ + public function sendPostRequest(array $parameters, string $url, string $accessKey) : array + { + $client = new Client([ + 'headers' => [ + 'charset' => 'utf-8', + 'Accept' => 'application/json', + 'X-NN-Access-Key' => base64_encode(str_replace(' ', '', $accessKey)), + ], + 'json' => $parameters + ]); + + try { + $response = $client->post($url)->getBody()->getContents(); + } catch (RequestException $requestException) { + return [ + 'result' => [ + 'status' => 'FAILURE', + 'status_code' => '106', + 'status_text' => $requestException->getMessage(), + ], + ]; + } + + return $this->unserializeData($response); + } + + /** + * Perform serialize data. + * + * @param array $data + * + * @return string + */ + public function serializeData(array $data): string + { + $result = '{}'; + + if (!empty($data)) { + $result = json_encode($data, JSON_UNESCAPED_SLASHES); + } + + return $result; + } + + /** + * Get action URL + * + * @param string $action + * + * @return string + */ + public function getActionEndpoint(string $action = '') : string + { + return $this->endpoint . str_replace('_', '/', $action); + } + + /** + * Perform unserialize data. + * + * @param string|null $data + * @param bool $needAsArray + * + * @return array|null + */ + public function unserializeData(string $data = null, bool $needAsArray = true): ?array + { + $result = []; + if (!empty($data)) { + $result = json_decode($data, $needAsArray, 512, JSON_BIGINT_AS_STRING); + } + + return $result; + } + + /** + * Return Shop Version. + * + * @return string + */ + public function getShopVersion(): string + { + return $this->shopVersion; + } + /** + * Get Shopware & Novalnet version information + * @param Context $context + * + * @return string + */ + public function getVersionInfo(Context $context) : string + { + return $this->shopVersion . '-NN' . '13.5.1'; + } + + /** + * get the novalnet Iframe From Url. + * + * @param SalesChannelContext $salesChannelContext + * @param mixed $transaction + * @param boolean $subscription + * + * @return string + */ + public function getNovalnetIframeUrl(SalesChannelContext $salesChannelContext, $transaction, bool $subscription = false) : string + { + $amount = 0; + if (method_exists($transaction, 'getOrder')) { + $amount = $this->amountInLowerCurrencyUnit($transaction->getOrder()->getPrice()->getTotalPrice()); + } elseif (method_exists($transaction, 'getCart')) { + $amount = $this->amountInLowerCurrencyUnit($transaction->getCart()->getprice()->getTotalPrice()); + } + + $requiredFields = ['amount' => $amount, 'currency' => $salesChannelContext->getCurrency()->getIsoCode() ? $salesChannelContext->getCurrency()->getIsoCode() : $salesChannelContext->getSalesChannel()->getCurrency()->getIsoCode()]; + + if (empty($salesChannelContext->getCustomer())) + { + return ''; + } + $subscriptionOrder = ''; + if (!empty($subscription)) { + $subscriptionOrder = 'SUBSCRIPTION'; + } + + $response = $this->getNovalnetIframeResponse($salesChannelContext->getSaleschannel()->getId(), $salesChannelContext->getCustomer()->getId(), $requiredFields, $salesChannelContext->getContext(), 'seamless_payment', $subscriptionOrder); + + if ($response['result']['status'] == 'SUCCESS') { + return $response['result']['redirect_url']; + }; + + return ''; + } + + /** + * get the theme name. + * + * @param Context $context + * @param string $saleschannelId + * + * @return string + */ + public function getThemeName(string $saleschannelId, Context $context) : string + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('salesChannels.id', $saleschannelId)); + $theme = $this->container->get('theme.repository')->search($criteria, $context); + $themecollection = $theme->getelements(); + $themeTechnicalName = ''; + foreach ($themecollection as $key => $data) { + if ($data->isActive() == 1) { + $themeTechnicalName = !is_null($data->getTechnicalName()) ? $data->getTechnicalName() : str_replace(' ', '', $data->getName()); + break; + } + } + return $themeTechnicalName; + } + + /** + * Returns the Novalnet backend configuration. + * + * @param string $salesChannelId + * + * @return array + */ + public function getNovalnetPaymentSettings(string $salesChannelId): array + { + $paymentSettings = $this->systemConfigService->getDomain( + 'NovalnetPayment.settings', + $salesChannelId, + true + ); + + if (!empty($paymentSettings['NovalnetPayment.settings.clientId'])) + { + $paymentSettings['NovalnetPayment.settings.clientId'] = str_replace(' ', '', $paymentSettings['NovalnetPayment.settings.clientId']); + } + + return $paymentSettings; + } + + /** + * Fetch the customer data from database + * + * @param string $customerId + * @param Context $context + * + * @return CustomerEntity|null + */ + public function getCustomerDetails(string $customerId, Context $context): ?CustomerEntity + { + $criteria = new Criteria([$customerId]); + $criteria->addAssociation('defaultBillingAddress'); + $criteria->addAssociation('defaultBillingAddress.country'); + $criteria->addAssociation('defaultShippingAddress'); + $criteria->addAssociation('defaultShippingAddress.country'); + $criteria->addAssociation('activeBillingAddress'); + $criteria->addAssociation('activeBillingAddress.country'); + $criteria->addAssociation('activeShippingAddress'); + $criteria->addAssociation('activeShippingAddress.country'); + $criteria->addAssociation('addresses'); + return $this->container->get('customer.repository')->search($criteria, $context)->first(); + } + + /** + * Get customer data + * + * @param CustomerEntity $customerEntity + * + * @return array + */ + public function getCustomerData(CustomerEntity $customerEntity) + { + $customer = []; + // Get billing details. + list($billingCustomer, $billingAddress) = $this->getAddress($customerEntity, 'billing'); + + if (! empty($billingCustomer)) { + $customer = $billingCustomer; + } + $customer ['billing'] = $billingAddress; + + if (!empty($customerEntity->getActiveBillingAddress()->getPhoneNumber())) { + $customer['tel'] = $customerEntity->getActiveBillingAddress()->getPhoneNumber(); + } + + list($shippingCustomer, $shippingAddress) = $this->getAddress($customerEntity, 'shipping'); + + // Add shipping details. + if (!empty($shippingAddress)) { + if ($billingAddress === $shippingAddress) { + $customer ['shipping'] ['same_as_billing'] = 1; + } else { + $customer ['shipping'] = $shippingAddress; + } + } + if (!empty($customerEntity->getSalutation())) { + $salutationKey = $customerEntity->getSalutation()->getSalutationKey(); + $customer ['gender'] = ($salutationKey == 'mr') ? 'm' : ($salutationKey == 'mrs' ? 'f' : 'u'); + } + + $customer['customer_ip'] = $this->getIp(); + if (empty($customer['customer_ip'])) { + $customer['customer_ip'] = $customerEntity->getRemoteAddress(); + } + $customer['customer_no'] = $customerEntity->getCustomerNumber(); + if(!empty($this->requestStack->getCurrentRequest())){ + $requested = $this->requestStack->getCurrentRequest(); + $customer['system_url'] = $requested->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL) + . $requested->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL); + } + return $customer; + } + + /** + * Get address + * + * @param CustomerEntity|null $customerEntity + * @param string $type + * + * @return array + */ + public function getAddress(CustomerEntity $customerEntity = null, string $type): array + { + $address = []; + $customer = []; + if (!empty($customerEntity)) { + if ($type === 'shipping') { + $addressData = $customerEntity->getActiveShippingAddress() ?? $customerEntity->getDefaultShippingAddress(); + } else { + $addressData = $customerEntity->getActiveBillingAddress() ?? $customerEntity->getDefaultBillingAddress(); + } + if (!empty($addressData) && !empty($addressData->getCountry())) { + $customer['first_name'] = $addressData->getFirstName(); + $customer['last_name'] = $addressData->getLastName(); + $customer['email'] = $customerEntity->getEmail(); + if (!empty($addressData->getCompany())) { + $address['company'] = $addressData->getCompany(); + } + + $address['street'] = $addressData->getStreet().' '.$addressData->getAdditionalAddressLine1().' '.$addressData->getAdditionalAddressLine2(); + $address['city'] = $addressData->getCity(); + $address['zip'] = $addressData->getZipCode(); + $address['country_code'] = $addressData->getCountry()->getIso(); + } + } + + return [$customer, $address]; + } + + /** + * Converting given amount into smaller unit + * + * @param float|null $amount + * + * @return float + */ + public function amountInLowerCurrencyUnit(float $amount = null): ?float + { + return (float) sprintf('%0.0f', ( $amount * 100 )); + } + + /** + * Get the system IP address. + * + * @param string $type + * @return string + */ + public function getIp(string $type = 'REMOTE') : string + { + $ipAddress = ''; + + // Check to determine the IP address type + if ($type === 'REMOTE') { + if (!empty($this->requestStack->getCurrentRequest())) { + $ipAddress = $this->requestStack->getCurrentRequest()->getClientIp(); + } + } else { + if (empty($_SERVER['SERVER_ADDR']) && !empty($_SERVER['SERVER_NAME'])) { + // Handled for IIS server + $ipAddress = gethostbyname($_SERVER['SERVER_NAME']); + } elseif (!empty($_SERVER['SERVER_ADDR'])) { + $ipAddress = $_SERVER['SERVER_ADDR']; + } + } + return $ipAddress; + } + + /** + * To fetch the shop language from context. + * + * @param Context $context + * @param boolean $formattedLocale + * @param string|null $languageId + * + * @return string + */ + public function getLocaleCodeFromContext(Context $context, bool $formattedLocale = false, string $languageId = null): string + { + $languageId = $languageId ?? $context->getLanguageId(); + $criteria = new Criteria([$languageId]); + $criteria->addAssociation('locale'); + $languageCollection = $this->languageRepository->search($criteria, $context)->getEntities(); + + $language = $languageCollection->get($languageId); + if (!$formattedLocale) { + if ($language === null || !($language->getLocale())) { + return 'DE'; + } + $locale = $language->getLocale(); + $lang = explode('-', $locale->getCode()); + return strtoupper($lang[0]); + } else { + $languageCode = 'de-DE'; + if ($language === null || !($language->getLocale())) { + return $languageCode; + } + $locale = $language->getLocale(); + if (!in_array($locale->getCode(), ['de-DE', 'en-GB'])) { + $languageID = Defaults::LANGUAGE_SYSTEM; + $languageCriteria = new Criteria([$languageID]); + $languageCriteria->addAssociation('locale'); + $language = $this->languageRepository->search($languageCriteria, $context)->first(); + $languageCode = $language->getLocale()->getCode(); + } else { + $languageCode = in_array($locale->getCode(), ['de-DE', 'en-GB']) ? $locale->getCode() : 'de-DE'; + } + + return $languageCode; + } + } + + /** + * Check for success status + * + * @param array $data + * + * @return bool + */ + public function isSuccessStatus(array $data): bool + { + return (bool) ((isset($data['result']['status']) && 'SUCCESS' === $data['result']['status']) || (isset($data['status']) && 'SUCCESS' === $data['status'])); + } + + /** + * Form Bank details comments. + * + * @param array $input + * @param Context $context + * @param string|null $languageId + * + * @return string + */ + public function formBankDetails(array $input, Context $context, string $languageId = null) : string + { + $comments = $this->formOrderComments($input, $context, $languageId); + $localeCode = $this->getLocaleCodeFromContext($context, true, $languageId); + $paymentType = $input['transaction']['payment_type']; + $translator = $this->translator; + + if (isset($input ['transaction']['amount']) && $input ['transaction']['amount']== 0 && in_array($paymentType, ['PREPAYMENT', 'INVOICE'])) { + $comments .= ''; + } elseif (!empty($input['transaction']['status']) && $input['transaction']['status'] != 'DEACTIVATED') { + if (($input['transaction']['status'] === 'PENDING' && in_array($paymentType, ['GUARANTEED_INVOICE', 'INSTALMENT_INVOICE'])) + + ) { + $comments .= $this->newLine . $translator->trans('NovalnetPayment.text.invoiceGuaranteePendingMsg', [], null, $localeCode); + } elseif (!empty($input ['transaction']['bank_details'])) { + $bankDetails = $input ['transaction']['bank_details']; + + if (isset($input['instalment']) && isset($input['instalment']['cycle_amount']) && $input['instalment']['cycle_amount'] !=0) { + $amountInBiggerCurrencyUnit = $this->amountInBiggerCurrencyUnit($input ['instalment']['cycle_amount'], isset($input ['instalment']['currency']) ? $input ['instalment']['currency'] : $input ['transaction']['currency'] ); + } elseif ($input['transaction']['amount'] != 0) { + $amountInBiggerCurrencyUnit = $this->amountInBiggerCurrencyUnit($input ['transaction']['amount'], $input ['transaction']['currency']); + } + + if (!empty($amountInBiggerCurrencyUnit)) { + if (in_array($input['transaction']['status'], [ 'CONFIRMED', 'PENDING' ], true) && ! empty($input ['transaction']['due_date'])) { + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.amountTransaferNoteWithDueDate', [], null, $localeCode), $amountInBiggerCurrencyUnit, date('d/m/Y', strtotime($input ['transaction']['due_date']))) . $this->newLine; + } else { + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.amountTransaferNote', [], null, $localeCode), $amountInBiggerCurrencyUnit) . $this->newLine; + } + } + + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.accountHolder', [], null, $localeCode), $bankDetails['account_holder']); + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.bank', [], null, $localeCode), $bankDetails['bank_name']); + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.bankPlace', [], null, $localeCode), $bankDetails['bank_place']); + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.iban', [], null, $localeCode), $bankDetails['iban']); + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.bic', [], null, $localeCode), $bankDetails['bic']) . $this->newLine; + + // Form reference comments. + $comments .= $this->newLine . $translator->trans('NovalnetPayment.text.paymentReferenceNote', [], null, $localeCode). $this->newLine; + /* translators: %s: TID */ + $comments .= sprintf($translator->trans('NovalnetPayment.text.paymentReference', [], null, $localeCode), '1', 'TID '. $input ['transaction']['tid']); + + if (! empty($input ['transaction']['invoice_ref'])) { + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.paymentReference', [], null, $localeCode), '2', $input ['transaction']['invoice_ref']). $this->newLine; + } + } + + if (!empty($input ['transaction']['due_date']) && $paymentType == 'CASHPAYMENT' ) { + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.slipExpiryDate', [], null, $localeCode), date('d/m/Y', strtotime($input ['transaction']['due_date']))) . $this->newLine . $this->newLine; + } + + if (!empty($input['transaction']['nearest_stores'])) { + + $comments .= $translator->trans('NovalnetPayment.text.cashpaymentStore', [], null, $localeCode) . $this->newLine; + + foreach ($input['transaction']['nearest_stores'] as $key => $nearestStore) { + $comments .= $nearestStore['store_name'] . $this->newLine; + $comments .= $nearestStore['street'] . $this->newLine; + $comments .= $nearestStore['city'] . $this->newLine; + $comments .= $nearestStore['zip'] . $this->newLine; + + if (!empty($nearestStore['country_code'])) { + $comments .= Countries::getName($nearestStore['country_code']) . $this->newLine . $this->newLine; + } + } + } + + + if (! empty($input['transaction']['partner_payment_reference'])) { + $amountInBiggerCurrencyUnit = $this->amountInBiggerCurrencyUnit($input ['transaction']['amount'], $input ['transaction']['currency']); + + $comments .= $this->newLine . $this->newLine . sprintf($translator->trans('NovalnetPayment.text.multibancoReference', [], null, $localeCode), $amountInBiggerCurrencyUnit); + $comments .= $this->newLine . sprintf($translator->trans('NovalnetPayment.text.paymentReference', [], null, $localeCode), '', $input['transaction']['partner_payment_reference']); + } + } + return $comments; + } + + /** + * Form payment comments. + * + * @param array $input + * @param Context $context + * @param string|null $languageId + * + * @return string + */ + public function formOrderComments(array $input, Context $context, string $languageId = null) : string + { + $comments = ''; + $paymentdata = []; + + $localeCode = $this->getLocaleCodeFromContext($context, true, $languageId); + $paymentType = $input['transaction']['payment_type']; + + if (!empty($input['isRecurringOrder']) && !empty($input['paymentData'])) { + $paymentdata = $input['paymentData']; + } else { + $paymentdata = $this->getSession('novalnetPaymentdata'); + } + + if (!empty($input ['transaction']['tid'])) { + $comments .= sprintf($this->translator->trans('NovalnetPayment.text.transactionId', [], null, $localeCode), $input ['transaction']['tid']) ; + if (!empty($input ['transaction'] ['test_mode'])) { + $comments .= $this->newLine . $this->translator->trans('NovalnetPayment.text.testOrder', [], null, $localeCode); + } + if (!empty($input['transaction']['status']) && $input['transaction']['status'] === 'PENDING' && + in_array($paymentType, ['GUARANTEED_DIRECT_DEBIT_SEPA', 'INSTALMENT_DIRECT_DEBIT_SEPA'])) { + $comments .= $this->newLine . $this->translator->trans('NovalnetPayment.text.sepaGuaranteePendingMsg', [], null, $localeCode); + } + } + + if (!empty($input['transaction']['status']) && $input['transaction']['status'] === 'CONFIRMED' && $input['transaction']['amount'] == 0 && in_array($paymentType, ['CREDITCARD', 'DIRECT_DEBIT_SEPA', 'GOOGLEPAY', 'DIRECT_DEBIT_ACH', 'APPLEPAY']) && ((!empty($paymentdata['booking_details']['payment_action']) && $paymentdata['booking_details']['payment_action'] == 'zero_amount') || (!empty($input['event']) && !empty($input['event']['checksum'])))) { + if (isset($input['custom']) && isset($input['custom']['input3']) && $input['custom']['input3'] == 'ZeroBooking') { + $comments .= $this->newLine . $this->newLine . $this->translator->trans('NovalnetPayment.text.zeroAmountAlertMsg', [], null, $localeCode); + } + } + + if ((isset($input['result']['status']) && 'FAILURE' === $input['result']['status'])) { + $comments .= $this->newLine . $this->newLine . $input ['result']['status_text']; + } + + return $comments; + } + + /** + * Converting given amount into bigger unit + * + * @param int $amount + * @param string $currency + * @param Context|null $context + * + * @return string + */ + public function amountInBiggerCurrencyUnit(int $amount, string $currency = '', Context $context = null) : ?string + { + $formatedAmount = (float) sprintf('%.2f', $amount / 100); + if (!empty($currency)) { + if (empty($context)) { + $context = Context::createDefaultContext(); + } + $formatedAmount = $this->currencyFormatter->formatCurrencyByLanguage($formatedAmount, $currency, $context->getLanguageId(), $context); + } + return (string) $formatedAmount; + } + + /** + * Retrieves messages from server response. + * + * @param array $data + * + * @return string + */ + public function getResponseText(array $data) : string + { + if (! empty($data ['result']['status_text'])) { + return $data ['result']['status_text']; + } + + if (! empty($data ['status_text'])) { + return $data ['status_text']; + } + return $this->translator->trans('NovalnetPayment.text.paymentError'); + } + + /** + * Generate Checksum Token + * + * @param Request $request + * @param string $accessKey + * @param string $txnSecret + * + * @return bool + */ + public function isValidChecksum(Request $request, string $accessKey, string $txnSecret): bool + { + if (! empty($request->get('checksum')) && ! empty($request->get('tid')) && ! empty($request->get('status')) && ! empty($accessKey) && ! empty($txnSecret)) { + $checksum = hash('sha256', $request->get('tid') . $txnSecret . $request->get('status') . strrev($accessKey)); + if ($checksum === $request->get('checksum')) { + return true; + } + } + return false; + } + + /** + * Fetch transaction details + * + * @param Request $request + * @param SalesChannelContext $salesChannelContext + * + * @return array + */ + public function fetchTransactionDetails(Request $request, SalesChannelContext $salesChannelContext): array + { + $paymentSettings = $this->getNovalnetPaymentSettings($salesChannelContext->getSalesChannel()->getId()); + $transactionDetails= []; + if ($request->get('tid')) { + $parameter = [ + 'transaction' => [ + 'tid' => $request->get('tid') + ], + 'custom' => [ + 'lang' => $this->getLocaleCodeFromContext($salesChannelContext->getContext()) + ] + ]; + $transactionDetails = $this->sendPostRequest($parameter, $this->getActionEndpoint('transaction_details'), $paymentSettings['NovalnetPayment.settings.accessKey']); + } + return $transactionDetails; + } + + /** + * Update Novalnet Transaction Data + * + * @param array $data + * @param Context $context + * + * @return void + */ + public function updateTransactionData(array $data, Context $context) + { + $this->container->get('novalnet_transaction_details.repository')->upsert([$data], $context); + } + + + /** + * To fetch the shop language from order id. + * Fixed language issue in translator. + * + * @param string $orderId + * @param boolean $getLanguageId + * + * @return string + */ + public function getLocaleFromOrder(string $orderId, $getLanguageId = false): string + { + $orderCriteria = new Criteria([$orderId]); + $orderCriteria->addAssociation('language'); + $orderCriteria->addAssociation('language.locale'); + $order = $this->container->get('order.repository')->search($orderCriteria, Context::createDefaultContext())->first(); + $locale = $order->getLanguage()->getLocale(); + + if ($getLanguageId) { + return $order->getLanguageId() ? $order->getLanguageId() : ''; + } + + if (!$locale) { + return 'de-DE'; + } + return in_array($locale->getCode(), ['de-DE', 'en-GB']) ? $locale->getCode() : 'de-DE'; + } + + /** + * Check mail if validate or not. + * + * @param string $mail + * + * @return bool + */ + public function isValidEmail($mail): bool + { + return (bool) (new EmailValidator())->isValid($mail, new RFCValidation()); + } + + /** + * Check mail if validate or not. + * + * @param array $paymentDetails + * @param array $customer + * + * @return string + */ + public function orderBackendPaymentData(array $paymentDetails, array $customer, Context $context): string + { + $result = ''; + + $customerid = $customer['id']; + $customerCriteria = new Criteria([$customerid]); + $customerDetails = $this->container->get('customer.repository')->search($customerCriteria, $context)->first(); + + if (!empty($paymentDetails)) { + $novalnetPaymentDetails = [ + 'novalnetOrderBackendParameters' => $paymentDetails + ]; + + $customerDetails->setcustomFields($novalnetPaymentDetails); + + $upsertData = [ + 'id' => $customerid, + 'customFields' => $customerDetails->getCustomFields() + ]; + + $this->container->get('customer.repository')->update([$upsertData], $context); + + $result = 'success'; + } + + + return $result; + } + + /** + * Set Session using key and data + * + * @param string $key + * @param $data + * + * @return void + */ + public function setSession(string $key, $data): void + { + $this->requestStack->getSession()->set($key, $data); + } + + /** + * Get Session using key + * + * @param string $key + */ + public function getSession(string $key) + { + return $this->requestStack->getSession()->get($key); + } + + /** + * Has Session using key + * + * @param string $key + * + * @return bool + */ + public function hasSession(string $key): bool + { + return $this->requestStack->getSession()->has($key); + } + + /** + * Remove Session + * + * @param string $key + * + */ + public function removeSession(string $key) + { + $this->requestStack->getSession()->remove($key); + } + + /** + * Get Updated Payment Type + * + * @param string $type + * @param boolean $getOldPayment + * + * @return string + */ + + public function getUpdatedPaymentType(string $type, bool $getOldPayment = false) : string + { + $types = [ + 'novalnetinvoice' => 'INVOICE', + 'novalnetprepayment' => 'PREPAYMENT', + 'novalnetsepa' => 'DIRECT_DEBIT_SEPA', + 'novalnetsepaguarantee' => 'GUARANTEED_DIRECT_DEBIT_SEPA', + 'novalnetinvoiceguarantee' => 'GUARANTEED_INVOICE', + 'novalnetcreditcard' => 'CREDITCARD', + 'novalnetinvoiceinstalment' => 'INSTALMENT_INVOICE', + 'novalnetsepainstalment' => 'INSTALMENT_DIRECT_DEBIT_SEPA', + 'novalnetcashpayment' => 'CASHPAYMENT', + 'novalnetmultibanco' => 'MULTIBANCO', + 'novalnetsofort' => 'ONLINE_TRANSFER', + 'novalnetideal' => 'IDEAL', + 'novalneteps' => 'EPS', + 'novalnettrustly' => 'TRUSTLY', + 'novalnetgiropay' => 'GIROPAY', + 'novalnetpaypal' => 'PAYPAL', + 'novalnetpostfinancecard' => 'POSTFINANCE_CARD', + 'novalnetpostfinance' => 'POSTFINANCE', + 'novalnetgooglepay' => 'GOOGLEPAY', + 'novalnetapplepay' => 'APPLEPAY', + 'novalnetwechatpay' => 'WECHATPAY', + 'novalnetalipay' => 'ALIPAY', + 'novalnetprzelewy24' => 'PRZELEWY24', + 'novalnetbancontact' => 'BANCONTACT', + 'novalnetonlinebanktransfer' => 'ONLINE_BANK_TRANSFER' + ]; + + if ($getOldPayment) { + $oldPaymentValues = array_flip($types); + return (!empty($oldPaymentValues[$type])) ? $oldPaymentValues[$type] : $type; + } + + if (!empty($types[$type])) { + return $types[$type]; + } + + return $type; + } + + /** + * Get Country Code + * + * @param string $countryId + * + * @return string + */ + + public function getCountry(string $countryId) : string + { + $countryCode = ''; + if (!empty($countryId)) { + $countryCriteria = new Criteria([$countryId]); + $countryDetails = $this->container->get('country.repository')->search($countryCriteria, Context::createDefaultContext())->first(); + $countryCode = $countryDetails->getIso(); + } + return $countryCode; + } + + /** + * Get Updated Payment Name + * + * @param string $type + * @param string $local + * + * @return string|null + */ + public function getUpdatedPaymentName($type, $local): ?string + { + return $this->translator->trans('NovalnetPayment.text.'.strtolower($type), [], null, $local); + } + + /** + * Get error message from session. + * + * @param string $transactionId + * @param string $saleschannelId + * + * @return string + */ + public function getNovalnetErrorMessage(string $transactionId, string $saleschannelId) : ?string + { + $errorMessage = ''; + if ($this->requestStack->getSession()->has('novalnetErrorMessage')) { + $errorMessage = $this->requestStack->getSession()->get('novalnetErrorMessage'); + $this->requestStack->getSession()->remove('novalnetErrorMessage'); + } + return $errorMessage; + } + + /** + * Return the iframe reponse data + * + * @param string $saleschannelId + * @param string $customerId + * @param array $requiredFields + * @param Context $context + * @param string $endPoint + * @param string $type + * + * @return array + */ + public function getNovalnetIframeResponse(string $saleschannelId, string $customerId, array $requiredFields, Context $context, string $endPoint, string $type = '') :array + { + $paymentSettings = $this->getNovalnetPaymentSettings($saleschannelId); + + $parameters['merchant'] = [ + 'signature' => $paymentSettings['NovalnetPayment.settings.clientId'], + 'tariff' => $paymentSettings['NovalnetPayment.settings.tariff'] + ]; + + $customer = $this->getCustomerDetails($customerId, $context); + + if (!empty($customer)) { + $parameters['customer'] = $this->getCustomerData($customer); + } + + $parameters['transaction'] = [ + 'amount' => $requiredFields['amount'], + 'currency' => $requiredFields['currency'], + 'system_ip' => $this->getIp('SYSTEM'), + 'system_name' => 'shopware6', + 'system_version' => $this->getVersionInfo($context) . '-NNT' . $this->getThemeName($saleschannelId, $context), + ]; + + $parameters['hosted_page'] = [ + 'hide_blocks' => ['ADDRESS_FORM', 'SHOP_INFO', 'LANGUAGE_MENU', 'TARIFF','HEADER'], + 'skip_pages' => ['CONFIRMATION_PAGE', 'SUCCESS_PAGE', 'PAYMENT_PAGE'], + 'type' => 'PAYMENTFORM' + ]; + + if (!empty($type)) { + $parameters['hosted_page']['display_payments_mode'] = [$type]; + } + + $parameters['custom']['lang'] = $this->getLocaleCodeFromContext($context); + + return $this->sendPostRequest($parameters, $this->getActionEndpoint($endPoint), str_replace(' ', '', $paymentSettings['NovalnetPayment.settings.accessKey'])); + } + + /** + * Return the payment request data + * + * @param float $amount + * @param string $orderNumber + * @param array $data + * @param SalesChannelContext $salesChannelContext + * + * @return array + */ + public function getNovalnetRequestData(float $amount, string $orderNumber, array $data, SalesChannelContext $salesChannelContext) :array + { + $paymentSettings = $this->getNovalnetPaymentSettings($salesChannelContext->getSalesChannel()->getId()); + + // Built merchant parameters. + $parameters['merchant'] = [ + 'signature' => $paymentSettings['NovalnetPayment.settings.clientId'], + 'tariff' => $paymentSettings['NovalnetPayment.settings.tariff'] + ]; + + // Built customer parameters. + if (!empty($salesChannelContext->getCustomer())) { + $parameters['customer'] = $this->getCustomerData($salesChannelContext->getCustomer()); + } + + //Build Transaction paramters. + $parameters['transaction'] = [ + 'amount' => $amount, + 'order_no' => $orderNumber, + 'currency' => $salesChannelContext->getCurrency()->getIsoCode() ? $salesChannelContext->getCurrency()->getIsoCode() : $salesChannelContext->getSalesChannel()->getCurrency()->getIsoCode(), + 'test_mode' => (int) $data['booking_details']['test_mode'], + 'payment_type' => $data['payment_details']['type'], + 'system_name' => 'Shopware6', + 'system_ip' => $this->getIp('SYSTEM'), + 'system_version' => $this->getVersionInfo($salesChannelContext->getContext()), + ]; + + $keys = ['account_holder', 'iban', 'bic', 'wallet_token', 'pan_hash', 'unique_id', 'account_number', 'routing_number']; + + foreach ($keys as $key) { + if (!empty($data['booking_details'][$key])) { + $parameters['transaction']['payment_data'][$key] = $data['booking_details'][$key]; + } + } + + if (!empty($data['booking_details']['payment_ref']['token'])) { + $parameters['transaction']['payment_data']['token'] = $data['booking_details']['payment_ref']['token']; + } + + if (!empty($data['booking_details']['birth_date'])) { + $parameters['customer']['birth_date'] = $data['booking_details']['birth_date']; + unset($parameters['customer']['billing']['company']); + } + + if (!empty($data['booking_details']['due_date'])) { + $parameters['transaction']['due_date'] = date('Y-m-d', strtotime('+' . $data['booking_details']['due_date'] . ' days')); + } + + if (isset($data['booking_details']['mobile']) && !empty($data['booking_details']['mobile'])) { + $parameters['customer']['mobile'] = $data['booking_details']['mobile']; + } + + if (!empty($data['booking_details']['payment_action']) && $data['booking_details']['payment_action'] == 'zero_amount') { + $parameters['transaction']['amount'] = 0; + $parameters['transaction']['create_token'] = 1; + } + + if (!empty($data['booking_details']['enforce_3d'])) { + $parameters['transaction']['enforce_3d'] = $data['booking_details']['enforce_3d']; + } + + if (!empty($data['booking_details']['create_token'])) { + $parameters['transaction']['create_token'] = $data['booking_details']['create_token']; + } + + //Build custom paramters. + $parameters['custom'] = [ + 'lang' => $this->getLocaleCodeFromContext($salesChannelContext->getContext()) + ]; + + return $parameters; + } + + /** + * Get customer address using the order address details. + * + * @param $address + * + * @return string|null + */ + public function getAddressId($address) : ?string + { + $criteria = new Criteria(); + $criteria->addFilter(new AndFilter([ + new EqualsFilter('customer_address.countryId', $address->getCountryId()), + new EqualsFilter('customer_address.firstName', $address->getFirstName()), + new EqualsFilter('customer_address.lastName', $address->getLastName()), + new EqualsFilter('customer_address.zipcode', $address->getZipcode()), + new EqualsFilter('customer_address.city', $address->getCity()), + new EqualsFilter('customer_address.street', $address->getStreet()), + new EqualsFilter('customer_address.company', $address->getCompany()), + ])); + + $address = $this->container->get('customer_address.repository')->search($criteria, Context::createDefaultContext())->first(); + return $address ? $address->getId() : null; + } + + /** + * Get user remote ip address + * + * @param string $novalnetHostIp + * + * @return bool + */ + public function checkWebhookIp(string $novalnetHostIp): bool + { + $ipKeys = ['HTTP_X_FORWARDED_HOST', 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR']; + + foreach ($ipKeys as $key) { + if (array_key_exists($key, $_SERVER) === true) { + if (in_array($key, ['HTTP_X_FORWARDED_HOST', 'HTTP_X_FORWARDED_FOR'])) { + $forwardedIps = (!empty($_SERVER[$key])) ? explode(",", $_SERVER[$key]) : []; + if (in_array($novalnetHostIp, $forwardedIps)) { + return true; + } + } + + if ($_SERVER[$key] == $novalnetHostIp) { + return true; + } + } + } + + return false; + } + +} diff --git a/src/Helper/NovalnetOrderTransactionHelper.php b/src/Helper/NovalnetOrderTransactionHelper.php new file mode 100644 index 0000000..f8fbb92 --- /dev/null +++ b/src/Helper/NovalnetOrderTransactionHelper.php @@ -0,0 +1,1318 @@ +helper = $helper; + $this->orderTransactionState = $orderTransactionState; + $this->translator = $translator; + $this->orderRepository = $orderRepository; + $this->orderTransactionRepository = $orderTransactionRepository; + $this->container = $container; + $this->paymentMethodRepository = $this->container->get('payment_method.repository'); + $this->novalnetTransactionRepository = $this->container->get('novalnet_transaction_details.repository'); + $this->stateMachineRepository = $this->container->get('state_machine_state.repository'); + $this->mailService = $mailService; + $this->mailTemplateRepository = $this->container->get('mail_template.repository'); + $this->mediaService = $mediaService; + $this->logger = $logger; + } + + /** + * Fetch Novalnet transaction data. + * + * @param string $orderNumber + * @param Context|null $context + * @param string|null $tid + * @param bool $changePayment + * + * @return NovalnetPaymentTransactionEntity + */ + public function fetchNovalnetTransactionData(string $orderNumber, $context = null, string $tid = null, bool $changePayment = false) : ? NovalnetPaymentTransactionEntity + { + $criteria = new criteria(); + if (!empty($tid)) { + if ($changePayment) { + $criteria->addFilter(new AndFilter([ + new EqualsFilter('novalnet_transaction_details.orderNo', $orderNumber), + new EqualsFilter('novalnet_transaction_details.tid', $tid) + ])); + } else { + $criteria->addFilter(new OrFilter([ + new EqualsFilter('novalnet_transaction_details.orderNo', $orderNumber), + new EqualsFilter('novalnet_transaction_details.tid', $tid) + ])); + } + } else { + $criteria->addFilter(new EqualsFilter('novalnet_transaction_details.orderNo', $orderNumber)); + } + + if (!$changePayment) { + $criteria->addFilter( + new MultiFilter( + 'OR', + [ + new EqualsFilter('novalnet_transaction_details.additionalDetails', null), + new NotFilter('AND', [new ContainsFilter('novalnet_transaction_details.additionalDetails', 'change_payment')]), + ] + ) + ); + } + + $criteria->addSorting( + new FieldSorting('createdAt', FieldSorting::DESCENDING) + ); + + /** @var NovalnetPaymentTransactionEntity|null */ + return $this->novalnetTransactionRepository->search($criteria, $context ?? Context::createDefaultContext())->first(); + } + + /** + * Fetch payment name. + * + * @param salesChannelContext $salesChannelContext + * @param string $orderNumber + * @param bool $changePayment + * + * @return string + */ + public function getPaymentName(SalesChannelContext $salesChannelContext, string $orderNumber, bool $changePayment = false): ?string + { + $paymentMethodName = ''; + $transactionData = $this->fetchNovalnetTransactionData((string) $orderNumber, $salesChannelContext->getContext(), null, $changePayment); + if (!empty($transactionData) && !empty($transactionData->getAdditionalDetails()) && strpos($transactionData->getAdditionalDetails(), 'payment_name') !== false) { + $additionalDetails = $this->helper->unserializeData($transactionData->getAdditionalDetails()); + $paymentMethodName = !empty($additionalDetails['payment_name']) ? $additionalDetails['payment_name'] : ''; + } else { + $order = $this->getOrderEntity($orderNumber, $salesChannelContext->getContext()); + if (!empty($order) && !empty($order->getTransactions())) { + $transaction = $order->getTransactions()->last(); + if (!empty($transaction->getCustomFields()) && (!empty($transaction->getCustomFields()['novalnet_payment_name']))) { + $paymentMethodName = $transaction->getCustomFields()['novalnet_payment_name']; + } + } + } + + return $paymentMethodName; + } + + /** + * Fetch order. + * + * @param string $orderNumber + * @param Context $context + * + * @return OrderEntity|null + */ + public function getOrderEntity(string $orderNumber, Context $context) : ?OrderEntity + { + $criteria = new criteria(); + $criteria->addFilter(new EqualsFilter('orderNumber', $orderNumber)); + $criteria->addAssociation('transactions'); + $criteria->addAssociation('currency'); + $criteria->addAssociation('salesChannel'); + $criteria->addAssociation('transactions.paymentMethod'); + $criteria->addSorting( + new FieldSorting('transactions.createdAt', FieldSorting::ASCENDING) + ); + $order = $this->orderRepository->search($criteria, $context)->first(); + + if ($order === null) { + return null; + } + /** @var OrderEntity|null */ + return $order; + } + + /** + * Get order. + * + * @param string $orderNumber + * @param Context $context + * + * @return OrderTransactionEntity|null + */ + public function getOrder(string $orderNumber, Context $context): ?OrderTransactionEntity + { + $order = $this->getOrderEntity($orderNumber, $context); + if ($order === null) { + return null; + } + + $transactionCollection = $order->getTransactions(); + + if ($transactionCollection === null) { + return null; + } + + $transaction = $transactionCollection->last(); + + if ($transaction === null) { + return null; + } + + return $transaction; + } + + + /** + * Post payment process + * + * @param OrderTransactionEntity $transaction + * @param Context $context + * @param string $comments + * @param array $upsertData + * @param bool $append + */ + public function postProcess(OrderTransactionEntity $transaction, Context $context, string $comments, array $upsertData = [], bool $append = true) : void + { + if (!empty($upsertData)) { + $this->novalnetTransactionRepository->update([$upsertData], $context); + } + if (!empty($transaction->getCustomFields()) && !empty($comments)) { + $oldComments = $transaction->getCustomFields()['novalnet_comments']; + + if (!empty($oldComments) && !empty($append)) { + $oldCommentsAppend = explode("&&", $oldComments); + + $oldCommentsAppend['0'] = $oldCommentsAppend['0'] . $this->newLine . $comments; + + $comments = implode('&&', $oldCommentsAppend); + } + $data = [ + 'id' => $transaction->getId(), + 'customFields' => [ + 'novalnet_comments' => $comments, + ], + ]; + $this->orderTransactionUpsert($data, $context); + } + } + + /** + * send novalnet mail. + * + * @param OrderEntity $order + * @param SalesChannelContext $salesChannelContext + * @param string $note + * @param boolean $instalmentRecurring + * + */ + public function prepareMailContent(OrderEntity $order, SalesChannelContext $salesChannelContext, string $note, $instalmentRecurring = false): void + { + if (!empty($order->getOrderCustomer())) { + $orderReference = $this->getOrderCriteria($order->getId(), $salesChannelContext->getContext(), $order->getOrderCustomer()->getCustomerId()); + try { + $emailConfigs = $this->helper->getNovalnetPaymentSettings($salesChannelContext->getSalesChannel()->getId()); + if (!empty($emailConfigs['NovalnetPayment.settings.emailMode']) && $emailConfigs['NovalnetPayment.settings.emailMode'] == 1) { + $this->sendMail($salesChannelContext, $orderReference, $note, $instalmentRecurring); + } + } catch (\RuntimeException $e) { + $this->setWarningMessage($e->getMessage()); + } + } + } + + + /** + * get the order reference details. + * + * @param string|null $orderId + * @param Context $context + * @param string|null $customerId + * + * @return OrderEntity|null + */ + public function getOrderCriteria(string $orderId = null, Context $context, string $customerId = null): ?OrderEntity + { + if (!empty($orderId)) { + $orderCriteria = new Criteria([$orderId]); + } else { + $orderCriteria = new Criteria([]); + } + if (!empty($customerId)) { + $orderCriteria->addFilter( + new EqualsFilter('order.orderCustomer.customerId', $customerId) + ); + } + $orderCriteria->addAssociation('orderCustomer.salutation'); + $orderCriteria->addAssociation('orderCustomer.customer'); + $orderCriteria->addAssociation('currency'); + $orderCriteria->addAssociation('stateMachineState'); + $orderCriteria->addAssociation('lineItems'); + $orderCriteria->addAssociation('transactions'); + $orderCriteria->addAssociation('transactions.paymentMethod'); + $orderCriteria->addAssociation('addresses'); + $orderCriteria->addAssociation('deliveries.shippingMethod'); + $orderCriteria->addAssociation('addresses.country'); + $orderCriteria->addAssociation('deliveries.shippingOrderAddress.country'); + $orderCriteria->addAssociation('salesChannel'); + $orderCriteria->addAssociation('salesChannel.domains'); + $orderCriteria->addAssociation('salesChannel.mailHeaderFooter'); + $orderCriteria->addAssociation('price'); + $orderCriteria->addAssociation('taxStatus'); + /** @var OrderEntity|null */ + return $this->orderRepository->search($orderCriteria, $context)->first(); + } + + /** + * send novalnet order mail. + * + * @param SalesChannelContext $salesChannelContext + * @param OrderEntity $order + * @param string $note + * @param boolean $instalmentRecurring + */ + public function sendMail(SalesChannelContext $salesChannelContext, OrderEntity $order, string $note, bool $instalmentRecurring = false): void + { + $customer = $order->getOrderCustomer(); + if (null === $customer) { + return; + } + $paymentName = ''; + $transaction = $order->getTransactions()->last(); + $instalmentInfo = []; + + $novalnetTransaction = $this->fetchNovalnetTransactionData($order->getOrderNumber(), $salesChannelContext->getContext()); + $novalnetTransaction->setPaymentType($this->helper->getUpdatedPaymentType($novalnetTransaction->getPaymentType())); + + if (!empty($novalnetTransaction)) { + $additionalDetails = $this->helper->unserializeData($novalnetTransaction->getAdditionalDetails()); + $paymentName = $this->getPaymentName($salesChannelContext , $order->getOrderNumber()); + + if (strpos($novalnetTransaction->getPaymentType(), 'INSTALMENT') !== false && $novalnetTransaction->getGatewayStatus() === 'CONFIRMED') { + $instalmentInfo = $additionalDetails; + } + } + + $mailTemplate = $this->getMailTemplate($salesChannelContext->getContext(), 'novalnet_order_confirmation_mail'); + + if (empty($mailTemplate)) { + return; + } + $data = new ParameterBag(); + $data->set( + 'recipients', + [ + $customer->getEmail() => $customer->getFirstName().' '.$customer->getLastName(), + ] + ); + $data->set('senderName', $mailTemplate->getTranslation('senderName')); + $data->set('salesChannelId', $order->getSalesChannelId()); + + $data->set('contentHtml', $mailTemplate->getTranslation('contentHtml')); + $data->set('contentPlain', $mailTemplate->getTranslation('contentPlain')); + + if ($instalmentRecurring) { + $data->set('subject', sprintf($this->translator->trans('NovalnetPayment.text.instalmentMailSubject'), $mailTemplate->getTranslation('senderName'), $order->getOrderNumber())); + } else { + $data->set('subject', $mailTemplate->getTranslation('subject')); + } + + if (!empty($mailTemplate->getMedia()) && !empty($mailTemplate->getMedia()->first())) + { + $data->set('binAttachments', $this->mailAttachments($mailTemplate, $salesChannelContext->getContext())); + } + + $finishNovalnetComments = explode("&&", $note); + + $notes = isset($finishNovalnetComments[0]) && !empty($finishNovalnetComments[0]) ? $finishNovalnetComments[0] : $note; + try { + $this->mailService->send( + $data->all(), + $salesChannelContext->getContext(), + [ + 'order' => $order, + 'note' => $notes, + 'instalment' => $instalmentRecurring, + 'salesChannel' => $order->getSalesChannel(), + 'context' => $salesChannelContext, + 'instalmentInfo' => $instalmentInfo, + 'paymentName' => $paymentName, + 'novalnetDetails' => $novalnetTransaction + ] + ); + } catch (\RuntimeException $e) { + $this->setWarningMessage($e->getMessage()); + } + } + + /** + * get the order mail template. + * + * @param Context $context + * @param string $technicalName + * + * @return MailTemplateEntity|null + */ + public function getMailTemplate(Context $context, string $technicalName): ? MailTemplateEntity + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('mailTemplateType.technicalName', $technicalName)); + $criteria->addAssociation('media.media'); + $criteria->setLimit(1); + + /** @var MailTemplateEntity|null $mailTemplate */ + $mailTemplate = $this->mailTemplateRepository->search($criteria, $context)->first(); + + return $mailTemplate; + } + + /** + * Refund Transaction data. + * + * @param NovalnetPaymentTransactionEntity $transactionData + * @param OrderTransactionEntity $transaction + * @param Context $context + * @param int $refundAmount + * @param Request $request + * + * @return array + */ + + public function refundTransaction(NovalnetPaymentTransactionEntity $transactionData, OrderTransactionEntity $transaction, Context $context, int $refundAmount, Request $request) : array + { + $parameter = []; + $paymentType = $this->helper->getUpdatedPaymentType($transactionData->getpaymentType()); + + $parameter['transaction'] = [ + 'tid' => !empty($request->get('instalmentCycleTid')) ? $request->get('instalmentCycleTid') : $transactionData->getTid(), + ]; + + $localeCode = $this->helper->getLocaleFromOrder($transaction->getOrderId()); + $parameter['custom'] = [ + 'shop_invoked' => 1, + 'lang' => strtoupper(substr($localeCode, 0, 2)), + ]; + + if ($request->get('reason')) { + $parameter['transaction']['reason'] = $request->get('reason'); + } + + if (!empty($refundAmount)) { + $parameter['transaction']['amount'] = $refundAmount; + } + $paymentSettings = $this->helper->getNovalnetPaymentSettings($this->getSalesChannelIdByOrderId($transaction->getOrderId(), $context)); + + $response = $this->helper->sendPostRequest($parameter, $this->helper->getActionEndpoint('transaction_refund'), $paymentSettings['NovalnetPayment.settings.accessKey']); + + if ($this->helper->isSuccessStatus($response)) { + $currency = !empty($response['transaction']['currency']) ? $response['transaction']['currency'] : $response['transaction']['refund']['currency']; + + if (!empty($response['transaction']['refund']['amount'])) { + $refundedAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit($response['transaction']['refund']['amount'], $currency, $context); + } else { + $refundedAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit($refundAmount, $transactionData->getCurrency(), $context); + } + + $message = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.refundComment', [], null, $localeCode), $transactionData->getTid(), $refundedAmountInBiggerUnit); + + if (! empty($response['transaction']['refund']['tid'])) { + $message .= sprintf($this->translator->trans('NovalnetPayment.text.refundCommentForNewTid', [], null, $localeCode), $response ['transaction']['refund']['tid']); + } + + $additionalDetails = $this->helper->unserializeData($transactionData->getAdditionalDetails()); + + if (preg_match('/INSTALMENT/', $paymentType)) { + $additionalDetails['InstalmentDetails'] = $this->updateInstalmentCycle($additionalDetails['InstalmentDetails'], $refundAmount, (string)$request->get('instalmentCycleTid'), $localeCode); + } + + $totalRefundedAmount = (int) $transactionData->getRefundedAmount() + (int) $refundAmount; + $this->postProcess($transaction, $context, $message, [ + 'id' => $transactionData->getId(), + 'refundedAmount' => $totalRefundedAmount, + 'gatewayStatus' => $response['transaction']['status'], + 'additionalDetails' => !empty($additionalDetails) ? $this->helper->serializeData($additionalDetails) : null, + + ]); + + if ($totalRefundedAmount >= $transactionData->getAmount()) { + try { + $this->orderTransactionState->refund($transaction->getId(), $context); + } catch (IllegalTransitionException $exception) { + $this->orderTransactionState->cancel($transaction->getId(), $context); + } + } + } + return $response; + } + + /** + * Refund Transaction data. + * + * @param NovalnetPaymentTransactionEntity $transactionData + * @param OrderTransactionEntity $transaction + * @param Context $context + * @param string $status + * + * + * @return array + */ + public function manageTransaction(NovalnetPaymentTransactionEntity $transactionData, OrderTransactionEntity $transaction, Context $context, string $status): array + { + $response = []; + $languageId = $this->helper->getLocaleFromOrder($transaction->getOrderId(), true); + $localeCode = $this->helper->getLocaleFromOrder($transaction->getOrderId()); + $paymentType = $this->helper->getUpdatedPaymentType($transactionData->getpaymentType()); + if ($status) { + $parameters = [ + 'transaction' => [ + 'tid' => $transactionData->getTid() + ], + 'custom' => [ + 'shop_invoked' => 1, + 'lang' => strtoupper(substr($localeCode, 0, 2)) + ] + ]; + $paymentSettings = $this->helper->getNovalnetPaymentSettings($this->getSalesChannelIdByOrderId($transaction->getOrderId(), $context)); + + $response = $this->helper->sendPostRequest($parameters, $this->helper->getActionEndpoint($status), $paymentSettings['NovalnetPayment.settings.accessKey']); + if ($this->helper->isSuccessStatus($response)) { + $message = ''; + $appendComments = true; + + $response['manageEvent'] = $status; + $responsePaymentType = $response['transaction']['payment_type']; + + if (! empty($response['transaction']['status'])) { + $transactionStatus = $response['transaction']['status']; + $upsertData = [ + 'id' => $transactionData->getId(), + 'gatewayStatus' => $transactionStatus + ]; + + if (in_array($transactionStatus, ['CONFIRMED', 'PENDING'])) { + $transactionAdditionDetails = $this->helper->unserializeData($transactionData->getAdditionalDetails()); + if (!empty($transactionData->getAdditionalDetails()) && !empty($paymentType) && preg_match('/INVOICE/', $paymentType) || preg_match('/PREPAYMENT/', $paymentType)) { + $appendComments = false; + $response['transaction']['bank_details'] = !empty($transactionAdditionDetails['bankDetails']) ? $transactionAdditionDetails['bankDetails'] : $transactionAdditionDetails; + $message .= $this->helper->formBankDetails($response, $context, $languageId) . $this->newLine; + } + + if ($transactionStatus == 'CONFIRMED') { + $upsertData['paidAmount'] = $transactionData->getAmount(); + + if (preg_match('/INSTALMENT/', $paymentType)) { + $upsertData['additionalDetails'] = $this->helper->unserializeData($transactionData->getAdditionalDetails()); + $response['transaction']['amount'] = $transactionData->getAmount(); + $upsertData['additionalDetails']['InstalmentDetails'] = $this->getInstalmentInformation($response, $localeCode); + $upsertData['additionalDetails'] = $this->helper->serializeData($upsertData['additionalDetails']); + } + } + + $message .= sprintf($this->translator->trans('NovalnetPayment.text.confirmMessage', [], null, $localeCode), date('d/m/Y H:i:s')). $this->newLine; + } elseif ($transactionStatus === 'DEACTIVATED') { + $appendComments = false; + $message .= $this->helper->formBankDetails($response, $context, $languageId); + $message .= $this->newLine . $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.faliureMessage', [], null, $localeCode), date('d/m/Y H:i:s')); + } + + $this->postProcess($transaction, $context, $message, $upsertData, $appendComments); + + if ($transactionStatus === 'CONFIRMED') { + if (!empty($paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->managePaymentStatus($completeStatus, $transaction->getId(), $context); + } else { + $this->orderTransactionState->paid($transaction->getId(), $context); + } + } elseif ($transactionStatus === 'PENDING') { + $this->managePaymentStatus('process', $transaction->getId(), $context); + } elseif ($transactionStatus !== 'PENDING') { + $this->orderTransactionState->cancel($transaction->getId(), $context); + } + } + } + } + return $response; + } + + + /** + * get the SalesChannel Id By OrderId + * + * @param string $orderId + * @param Context $context + * + * @return string + */ + private function getSalesChannelIdByOrderId(string $orderId, Context $context): string + { + $order = $this->orderRepository->search(new Criteria([$orderId]), $context)->first(); + if ($order === null) { + throw new \RuntimeException('Order not founded'); + } + + return $order->getSalesChannelId(); + } + + /** + * Update Novalnet instalment cycles + * + * @param array $instalmentDetails + * @param int $amount + * @param string $instalmentCycleTid + * @param string $localeCode + * + * @return array + */ + public function updateInstalmentCycle(array $instalmentDetails, int $amount, string $instalmentCycleTid, string $localeCode): array + { + foreach ($instalmentDetails as $key => $values) { + if ($values['reference'] == $instalmentCycleTid) { + $instalmentDetails[$key]['refundAmount'] = (int) $values['refundAmount'] + $amount; + if ($instalmentDetails[$key]['refundAmount'] >= $values['amount']) { + $instalmentDetails[$key]['status'] = $this->translator->trans('NovalnetPayment.text.refundedMsg', [], null, $localeCode); + } + } + } + + return $instalmentDetails; + } + + /** + * instalment Cancel Type + * + * @param NovalnetPaymentTransactionEntity $transactionData + * @param OrderTransactionEntity $transaction + * @param Context $context + * @param Request $request + * @return array + */ + + public function instalmentCancelType(NovalnetPaymentTransactionEntity $transactionData, OrderTransactionEntity $transaction, Context $context, Request $request) : array + { + $localeCode = $this->helper->getLocaleFromOrder($transaction->getOrderId()); + + $parameter = [ + 'instalment' => [ + 'tid' => $transactionData->getTid(), + 'cancel_type' => $request->get('cancelType') + ], + 'custom' => [ + 'shop_invoked' => 1, + 'lang' => strtoupper(substr($localeCode, 0, 2)) + ] + ]; + + $paymentSettings = $this->helper->getNovalnetPaymentSettings($this->getSalesChannelIdByOrderId($transaction->getOrderId(), $context)); + $response = $this->helper->sendPostRequest($parameter, $this->helper->getActionEndpoint('instalment_cancel'), $paymentSettings['NovalnetPayment.settings.accessKey']); + + if ($this->helper->isSuccessStatus($response)) { + $additionalDetails = $this->helper->unserializeData($transactionData->getAdditionalDetails()); + if (isset($response['transaction']['refund'])) { + $refundedAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit($response['transaction']['refund']['amount'], $transactionData->getCurrency(), $context); + $message = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.instalmentRefundComment', [], null, $localeCode), $transactionData->getTid(), date('Y-m-d H:i:s'), $refundedAmountInBiggerUnit); + $totalRefundedAmount = $transactionData->getAmount(); + } else { + $message = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.instalmentRemainRefundComment', [], null, $localeCode), $transactionData->getTid(), date('Y-m-d H:i:s')); + $totalRefundedAmount = $transactionData->getRefundedAmount(); + foreach ($additionalDetails['InstalmentDetails'] as $instalment) { + $totalRefundedAmount += empty($instalment['reference']) ? $instalment['amount'] : 0; + } + } + $additionalDetails['InstalmentDetails'] = $this->updateInstalmentCancel($additionalDetails['InstalmentDetails'], $response['instalment']['cancel_type'], $localeCode); + $additionalDetails['cancelType'] = $request->get('cancelType'); + + $this->postProcess($transaction, $context, $message, [ + 'id' => $transactionData->getId(), + 'refundedAmount' => $totalRefundedAmount, + 'gatewayStatus' => $response['transaction']['status'], + 'additionalDetails' => $this->helper->serializeData($additionalDetails), + ]); + + if ($totalRefundedAmount >= $transactionData->getAmount()) { + try { + $this->orderTransactionState->cancel($transaction->getId(), $context); + } catch (IllegalTransitionException $exception) { + + } + } + } + + return $response; + } + + /** + * Update Novalnet instalment cycles + * + * @param array $instalmentDetails + * @param string|null $cycleType + * @param string $localeCode + * + * @return array + */ + public function updateInstalmentCancel(array $instalmentDetails, ?string $cycleType, string $localeCode): array + { + foreach ($instalmentDetails as $key => $values) { + if ($cycleType == 'ALL_CYCLES' || empty($cycleType)) { + $instalmentDetails[$key]['refundAmount'] = !empty($values['reference']) ? $values['amount'] : 0; + $instalmentDetails[$key]['status'] = !empty($values['reference']) ? $this->translator->trans('NovalnetPayment.text.refundedMsg', [], null, $localeCode) : $this->translator->trans('NovalnetPayment.text.cancelMsg', [], null, $localeCode); + } elseif ($cycleType == 'REMAINING_CYCLES' && empty($values['reference'])) { + $instalmentDetails[$key]['status'] = $this->translator->trans('NovalnetPayment.text.cancelMsg', [], null, $localeCode); + } + } + return $instalmentDetails; + } + + /** + * Zero book amount . + * + * @param NovalnetPaymentTransactionEntity $transactionData + * @param OrderEntity $order + * @param Context $context + * @param int $bookAmount + * + * + * @return array + */ + public function bookOrderAmount(NovalnetPaymentTransactionEntity $transactionData, OrderEntity $order, Context $context, int $bookAmount) : array + { + $localeCode = $this->helper->getLocaleFromOrder($order->getId()); + $response = []; + $parameters = []; + $paymentSettings = $this->helper->getNovalnetPaymentSettings($order->getSalesChannelId()); + $orderCollection = $this->getOrderCriteria($order->getId(), $context, $order->getOrderCustomer()->getCustomerId()); + $parameters['merchant'] = [ + 'signature' => $paymentSettings['NovalnetPayment.settings.clientId'], + 'tariff' => $paymentSettings['NovalnetPayment.settings.tariff'] + ]; + + // Built custom parameters. + $parameters['custom'] = [ + 'lang' => $localeCode + ]; + $parameters['transaction'] = [ + 'order_no' => $order->getOrderNumber(), + 'currency' => $transactionData->getCurrency(), + 'payment_type' => $transactionData->getPaymentType(), + 'system_name' => 'Shopware6', + 'system_ip' => $this->helper->getIp('SYSTEM'), + 'system_version' => $this->helper->getVersionInfo($context), + ]; + $parameters['transaction']['payment_type'] = $transactionData->getPaymentType(); + $parameters['customer'] = $this->bookAmountRequest($orderCollection, $context); + $parameters['customer']['email'] = $order->getOrderCustomer()->getEmail(); + $parameters['customer']['customer_ip'] = $order->getOrderCustomer()->getRemoteAddress(); + $parameters['customer']['customer_no'] = $order->getOrderCustomer()->getCustomerNumber(); + $parameters['transaction']['amount'] = $bookAmount; + $parameters['transaction']['payment_data'] = ['token' => $transactionData->getTokenInfo()]; + + $response = $this->helper->sendPostRequest($parameters, $this->helper->getActionEndpoint('payment'), $paymentSettings['NovalnetPayment.settings.accessKey']); + + if ($this->helper->isSuccessStatus($response)) { + $bookAmountInBiggerUnit = $this->helper->amountInBiggerCurrencyUnit($response['transaction'] ['amount'], $response['transaction'] ['currency'], $context); + $message = $this->newLine . sprintf($this->translator->trans('NovalnetPayment.text.bookedComment', [], null, $localeCode), $bookAmountInBiggerUnit, $response['transaction'] ['tid']); + $transaction = $this->getOrder($order->getOrderNumber(), $context); + + $this->postProcess($transaction, $context, $message, [ + 'id' => $transactionData->getId(), + 'tid' => $response['transaction']['tid'], + 'amount' => $response['transaction']['amount'], + 'paidAmount' => $response['transaction']['amount'], + 'gatewayStatus' => $response['transaction']['status'], + ]); + + try { + if (!empty($paymentSettings['NovalnetPayment.settings.completeStatus'])) { + $completeStatus = $paymentSettings['NovalnetPayment.settings.completeStatus']; + $this->managePaymentStatus($completeStatus, $transaction->getId(), $context); + } else { + $this->orderTransactionState->paid($transaction->getId(), $context); + } + } catch (IllegalTransitionException $exception) { + + } + } + return $response; + } + + /** + * Form instalment information. + * + * @param array $response + * @param string $localeCode + * + * @return array + */ + public function getInstalmentInformation(array $response, string $localeCode): array + { + $instalmentData = $response['instalment']; + $additionalDetails = []; + + if (!empty($instalmentData['cycle_dates'])) { + $futureInstalmentDate = $instalmentData['cycle_dates']; + foreach (array_keys($futureInstalmentDate) as $cycle) { + $additionalDetails[$cycle] = [ + 'amount' => $instalmentData['cycle_amount'], + 'cycleDate' => !empty($futureInstalmentDate[$cycle + 1]) ? date('Y-m-d', strtotime($futureInstalmentDate[$cycle + 1])) : '', + 'cycleExecuted' => '', + 'dueCycles' => '', + 'paidDate' => '', + 'status' => $this->translator->trans('NovalnetPayment.text.pendingMsg', [], null, $localeCode), + 'reference' => '', + 'refundAmount' => 0, + ]; + + if ($cycle == count($instalmentData['cycle_dates'])) { + $amount = $response['transaction']['amount'] - ($instalmentData['cycle_amount'] * ($cycle - 1)); + $additionalDetails[$cycle] = array_merge($additionalDetails[$cycle], [ + 'amount' => $amount + ]); + } + + if ($cycle == 1) { + $additionalDetails[$cycle] = array_merge($additionalDetails[$cycle], [ + 'cycleExecuted' => !empty($instalmentData['cycles_executed']) ? $instalmentData['cycles_executed'] : '', + 'dueCycles' => !empty($instalmentData['pending_cycles']) ? $instalmentData['pending_cycles'] : '', + 'paidDate' => date('Y-m-d'), + 'status' => $this->translator->trans('NovalnetPayment.text.paidMsg', [], null, $localeCode), + 'reference' => (string) $response['transaction']['tid'], + 'refundAmount' => 0, + ]); + } + } + } + + return $additionalDetails; + } + + /** + * Get Subscription Details. + * + * @param Context $context + * @param string $orderNumber + * + * @return array + */ + public function getSubscriptionDetails(Context $context, string $orderNumber) : array + { + $criteria = new criteria(); + $criteria->addFilter(new EqualsFilter('novalnet_transaction_details.orderNo', $orderNumber)); + $criteria->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING)); + $orderDetails = $this->novalnetTransactionRepository->search($criteria, $context)->first(); + $subscription = $this->helper->unserializeData($orderDetails->getAdditionalDetails()); + $datas = []; + if (!empty($subscription['subscription'])) { + $subscription['subscription']['booking_details']['payment_action'] = 'payment'; + return $subscription['subscription']; + } else { + $datas = [ + 'payment_details' => [ + 'type' => $this->helper->getUpdatedPaymentType($orderDetails->getPaymentType()), + 'process_mode' => 'direct', + ], + 'booking_details' => [ + 'test_mode' => 0, + 'payment_action' => 'payment' + ] + ]; + } + + return $datas; + } + + /** + * Fetch Novalnet last transaction data by customer id . + * + * @param string $customerNumber + * @param string $orderNumber + * @param string $paymentType + * @param Context|null $context + * + * @return NovalnetPaymentTransactionEntity|null + */ + public function fetchNovalnetReferenceData(string $customerNumber, string $orderNumber, string $paymentType, Context $context = null): ? NovalnetPaymentTransactionEntity + { + + $oldpaymentName = $this->helper->getUpdatedPaymentType($paymentType, true); + $criteria = new Criteria(); + $criteria->addFilter(new AndFilter([ + new EqualsFilter('novalnet_transaction_details.customerNo', $customerNumber), + new EqualsFilter('novalnet_transaction_details.orderNo', $orderNumber), + new EqualsAnyFilter('novalnet_transaction_details.paymentType', [$paymentType, $oldpaymentName]), + ])); + + if ($paymentType == 'GUARANTEED_INVOICE') { + $criteria->addFilter(new ContainsFilter('novalnet_transaction_details.additionalDetails', 'dob')); + } + + $criteria->addSorting( + new FieldSorting('createdAt', FieldSorting::DESCENDING) + ); + /** @var NovalnetPaymentTransactionEntity|null */ + $novalnetReferenceData = $this->novalnetTransactionRepository->search($criteria, $context ?? Context::createDefaultContext())->first(); + + return $novalnetReferenceData; + } + + + /** + * Zero book amount Request . + * + * @param OrderEntity $order + * @param Context $context + + * @return array + */ + public function bookAmountRequest(OrderEntity $order, Context $context): array + { + $customer = []; + $billingAddress = $shippingAddress = []; + $addresses = $order->getAddresses()->getelements(); + foreach ($addresses as $id => $value) { + if ($order->getBillingAddressId() == $id) { + $billingAddress = $this->customAddress($value); + $customer ['billing'] = $billingAddress; + $customer['first_name'] = $value->getFirstName(); + $customer['last_name'] = $value->getLastName(); + } + } + + if(!empty($order->getDeliveries()->first())){ + $shipping = $order->getDeliveries()->first(); + $shippingAddress = $this->customAddress($shipping->getshippingOrderAddress()); + } else { + $shippingAddress = !empty($billingAddress) ? $billingAddress : []; + } + + if (!empty($shippingAddress)) { + if ($billingAddress === $shippingAddress) { + $customer ['shipping'] ['same_as_billing'] = 1; + } else { + $customer ['shipping'] = $shippingAddress; + } + } + + return $customer; + } + + public function customAddress($addressData) : array + { + $address = []; + + if (!empty($addressData) && !empty($addressData->getCountry())) { + if (!empty($addressData->getCompany())) { + $address['company'] = $addressData->getCompany(); + } + + $address['street'] = $addressData->getStreet().' '.$addressData->getAdditionalAddressLine1().' '.$addressData->getAdditionalAddressLine2(); + $address['city'] = $addressData->getCity(); + $address['zip'] = $addressData->getZipCode(); + $address['country_code'] = $addressData->getCountry()->getIso(); + } + return $address; + } + + /** + * Update payment transaction status. + * + * @param string $paymentStatus + * @param string $transactionId + * @param Context $context + * + * + */ + public function managePaymentStatus(string $paymentStatus, string $transactionId, Context $context) + { + $status = strtolower($paymentStatus); + + if ($status == 'paid') { + $this->orderTransactionState->paid($transactionId, $context); + } elseif (in_array($status, ['cancel', 'cancelled'])) { + $this->orderTransactionState->cancel($transactionId, $context); + } elseif ($status =='failed') { + $this->orderTransactionState->fail($transactionId, $context); + } elseif ($status == 'paidpartially') { + $this->orderTransactionState->payPartially($transactionId, $context); + } elseif (in_array($status,['process', 'pending'])) { + try { + $criteria = new criteria(); + $criteria->addFilter(new EqualsFilter('technicalName', 'in_progress')); + $criteria->addFilter(new EqualsFilter('stateMachine.technicalName', 'order_transaction.state')); + $criteria->addAssociation('stateMachine'); + $status = $this->stateMachineRepository->search($criteria, $context)->first(); + if (!empty($status)) { + $connection = $this->container->get(Connection::class); + $connection->exec(sprintf(" + UPDATE `order_transaction` + SET `state_id` = UNHEX('%s') WHERE `id` = UNHEX('%s'); + ", $status->getId(), $transactionId)); + } + } catch (IllegalTransitionException $exception) { + + } + } elseif ($status =='open') { + $this->orderTransactionState->reopen($transactionId, $context); + } elseif ($status =='authorized') { + $this->orderTransactionState->authorize($transactionId, $context); + } + } + + /** + * Update payment transaction status. + * + * @param string $message + * + * @return void + */ + public function setWarningMessage(string $message): void + { + $this->logger->warning($message); + } + + public function updateSubscriptionData(string $status, string $paymentMethodId, string $orderId) + { + $connection = $this->container->get(Connection::class); + if (method_exists($connection, 'getSchemaManager')) { + $schemaManager = $connection->getSchemaManager(); + } else { + $schemaManager = $connection->createSchemaManager(); + } + + if ($schemaManager->tablesExist(array('novalnet_subscription')) == true) { + $data = $connection->fetchOne(sprintf("SELECT `id` FROM `novalnet_subscription` WHERE `order_id` = UNHEX('%s')", $orderId)); + if (!empty($data)) { + if (in_array($status, ['ON_HOLD', 'PENDING', 'CONFIRMED'])) { + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `status` = 'ACTIVE', payment_method_id = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $paymentMethodId, $orderId)); + $connection->exec(sprintf("UPDATE `novalnet_subs_cycle` SET `status` = 'SUCCESS', payment_method_id = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $paymentMethodId, $orderId)); + } else { + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `status` = 'CANCELLED', cancel_reason = 'Parent order getting failed', cancelled_at = '%s', payment_method_id = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", date('Y-m-d H:i:s'), $paymentMethodId, $orderId)); + $connection->exec(sprintf("UPDATE `novalnet_subs_cycle` SET `status` = 'FAILURE', payment_method_id = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $paymentMethodId, $orderId)); + } + } + } + } + + /** + * Get the mail attachment + * + * @param MailTemplateEntity $mailTemplate + * @param Context $context + * + * @retrun array + */ + + public function mailAttachments(MailTemplateEntity $mailTemplate, Context $context) : array + { + foreach ($mailTemplate->getMedia() ?? [] as $mailTemplateMedia) { + + if ($mailTemplateMedia->getMedia() === null) { + continue; + } + + $attachments[] = $this->mediaService->getAttachment( + $mailTemplateMedia->getMedia(), + $context + ); + + } + + return $attachments ?? []; + } + + public function updateChangePayment(array $input, string $orderId, Context $context, bool $ordeUpdatedPayment = false) + { + $paymentType = $input['transaction']['payment_type'] ?? 'NOVALNET_PAYMENT'; + + // insert novalnet transaction details + $insertData = [ + 'id' => Uuid::randomHex(), + 'paymentType' => $paymentType, + 'paidAmount' => 0, + 'tid' => $input['transaction']['tid'], + 'currency' => $input['transaction']['currency'], + 'gatewayStatus' => $input['transaction']['status'], + 'customerNo' => !empty($input['customer']['customer_no']) ? $input['customer']['customer_no'] : '', + 'additionalDetails' => [ + 'payment_name' => $this->helper->getUpdatedPaymentName($paymentType, $this->helper->getLocaleCodeFromContext($context)), + 'change_payment' => true + ] + ]; + + if(empty($ordeUpdatedPayment)){ + $data = [ + 'payment_details' => [ + 'type' => $paymentType, + 'process_mode' => 'direct', + ], + 'booking_details' => [ + 'test_mode' => $input ['transaction']['test_mode'] + ], + 'aboId' => $input ['custom']['subscriptionId'], + 'paymentMethodId' => $input ['custom']['paymentMethodId'] + ]; + $amount = $input['transaction']['amount']; + $orderNo = $input['transaction']['order_no']; + } else { + + $data = $input['paymentData']; + $amount = 0; + $orderNo = $input['custom']['inputval2']; + } + + $insertData['amount'] = $amount; + $insertData['orderNo'] = $orderNo; + + $insertData['additionalDetails']['subscription'] = $data; + + if (!empty($input['transaction']['payment_data']['token'])) { + $insertData['tokenInfo'] = $input['transaction']['payment_data']['token']; + } + + $insertData['additionalDetails'] = $this->helper->serializeData($insertData['additionalDetails']); + + // Upsert data into novalnet_transaction_details.repository + $this->helper->updateTransactionData($insertData, $context); + + if(isset($input['event']) && isset($input['event']['type'])){ + $connection = $this->container->get(Connection::class); + if (method_exists($connection, 'getSchemaManager')) { + $schemaManager = $connection->getSchemaManager(); + } else { + $schemaManager = $connection->createSchemaManager(); + } + if ($schemaManager->tablesExist(array('novalnet_subscription')) == true) { + $data = $connection->fetchOne(sprintf("SELECT `id` FROM `novalnet_subscription` WHERE `order_id` = UNHEX('%s')", $orderId)); + $subCycleData = $connection->fetchAssociative(sprintf("SELECT * FROM `novalnet_subs_cycle` WHERE `order_id` = UNHEX('%s')", $orderId)); + + if (!empty($data)) { + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `payment_method_id` = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $input ['custom']['paymentMethodId'], $orderId)); + $connection->exec(sprintf("UPDATE `novalnet_subs_cycle` SET `payment_method_id` = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $input ['custom']['paymentMethodId'], $orderId)); + } + + if(!empty($subCycleData) && isset($input['custom']['input2']) && $input['custom']['input2'] == 'subParentOrderNumber'){ + + $connection->exec(sprintf("UPDATE `novalnet_subs_cycle` SET `status` = 'SUCCESS', payment_method_id = UNHEX('%s') WHERE `order_id` = UNHEX('%s')", $input ['custom']['paymentMethodId'], $orderId)); + + $subscriptiondata = $connection->fetchAssociative(sprintf("SELECT * FROM `novalnet_subscription` WHERE `id` = UNHEX('%s')", $input['custom']['inputval4'])); + $subscriptionDate = strtotime($subscriptiondata['next_date']); + $formatDate = date('Y-m-d H:i:s',$subscriptionDate); + $nextDate = $this->getUpdatedNextDate((int) $subscriptiondata['interval'], $this->getSubscriptionUnitValue($subscriptiondata['unit']), $formatDate, $subscriptiondata, $input ['custom']['inputval4']); + + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `status` = 'ACTIVE', next_date = '%s', payment_method_id = UNHEX('%s') WHERE `id` = UNHEX('%s')", $nextDate, $input ['custom']['paymentMethodId'], $input['custom']['inputval4'])); + + if((($subscriptiondata['length'] > $subCycleData['cycles'] ) || ($subscriptiondata['length'] == 0))){ + $id = Uuid::randomHex(); + $cycleDate = $this->getFormattedDate((int) $subscriptiondata['interval'], $this->getSubscriptionUnitValue($subscriptiondata['unit']), date('Y-m-d H:i:s')); + $connection->exec(sprintf("INSERT INTO `novalnet_subs_cycle` (`id`, `subs_id`, `order_id`, `amount`, `interval`, `period`, `payment_method_id`, `cycles`, `cycle_date`, `status`) VALUES(UNHEX('%s'),UNHEX('%s'),null,'%s','%s','%s',UNHEX('%s'),'%s','%s','%s')", $id,$input['custom']['inputval4'],$subCycleData['amount'],$subCycleData['interval'],$subCycleData['period'],$input ['custom']['paymentMethodId'],$subCycleData['cycles'] + 1,$cycleDate, 'PENDING')); + + } + + if(($subscriptiondata['length'] == $subCycleData['cycles'] )){ + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `status` = 'PENDING_CANCEL', payment_method_id = UNHEX('%s') WHERE `id` = UNHEX('%s')", $input ['custom']['paymentMethodId'], $input ['custom']['inputval4'])); + } + + } + } + } + } + + /** + * Update the order transaction details + * + * @param array $data + * @param Context $context + + */ + public function orderTransactionUpsert(array $data, Context $context) + { + $this->orderTransactionRepository->upsert([$data], $context); + + } + + /** + * Returns the formatted date. + * + * @param int $interval + * @param string $period + * @param string $date + * + * @return string + */ + public function getFormattedDate(int $interval, string $period, $date): string + { + return date('Y-m-d H:i:s', strtotime('+ '. $interval . $period, strtotime($date))); + } + + public function getSubscriptionUnitValue(string $aboUnit) + { + return $unit = ($aboUnit == 'd') ? 'days' : ($aboUnit == 'w' ? 'weeks' : ($aboUnit == 'm' ? 'months' : 'years')); + } + + + /** + * Returns the formatted date. + * + * @param int $interval + * @param string $period + * @param string $date + * @param array $subscription + * @param string $id + * + * @return string + */ + public function getUpdatedNextDate(int $interval, string $period, $date, array $subscription , string $id): string + { + if (!empty($subscription) && !empty($subscription['last_day_month']) && $period == 'months') { + return date('Y-m-d H:i:s', strtotime("last day of +$interval month", strtotime($date))); + } elseif ($period == 'months') { + if (date("d", strtotime($date)) == date("d", strtotime($date."+$interval month"))) { + $nextDate = date('Y-m-d H:i:s', strtotime($date." +$interval month")); + } else { + if (!empty($subscription)) { + $connection = $this->container->get(Connection::class); + $connection->exec(sprintf("UPDATE `novalnet_subscription` SET `last_day_month` = '%s' WHERE `id` = UNHEX('%s')", 1, $id)); + } + $nextDate = date('Y-m-d H:i:s', strtotime("last day of +$interval month", strtotime($date))); + } + + return $nextDate; + } + + return date('Y-m-d H:i:s', strtotime('+ '. $interval . $period, strtotime($date))); + } + + +} diff --git a/src/Installer/MediaProvider.php b/src/Installer/MediaProvider.php new file mode 100644 index 0000000..78aeb8c --- /dev/null +++ b/src/Installer/MediaProvider.php @@ -0,0 +1,83 @@ +mediaService = $mediaService; + $this->connection = $container->get(Connection::class); + } + + /** + * Get Media ID + * + * @param string $paymentMethod + * @param Context $context + * + * @return string|null + */ + public function getMediaId(string $paymentMethod, Context $context): ?string + { + $fileName = $paymentMethod . '-icon'; + + $iconId = $this->connection->fetchOne('SELECT `id` FROM `media` WHERE file_name = "'.$fileName.'"'); + + // Return the already existing file in the same name. + if ($iconId) { + return Uuid::fromBytesToHex($iconId); + } + + // Insert media file to library. + $file = file_get_contents(dirname(__DIR__, 1).'/Resources/public/storefront/assets/img/'.$paymentMethod.'.png'); + $mediaId = ''; + if ($file) { + $mediaId = $this->mediaService->saveFile($file, 'png', 'image/png', $fileName, $context, 'Novalnet Payment - Icons', null, false); + } + return $mediaId; + } +} diff --git a/src/Installer/PaymentMethodInstaller.php b/src/Installer/PaymentMethodInstaller.php new file mode 100644 index 0000000..20828af --- /dev/null +++ b/src/Installer/PaymentMethodInstaller.php @@ -0,0 +1,1130 @@ + [ + 'name' => 'Novalnet Zahlung', + 'description' => 'Bieten Sie Ihren Kunden auf sichere und vertrauenswürdige Weise alle weltweit unterstützten Zahlungsarten. Mit Novalnet können Sie Ihre Verkäufe steigern und Ihren Kunden ein ansprechendes Zahlungserlebnis aus einem Guss bieten.', + ], + 'en-GB' => [ + 'name' => 'Novalnet Payment', + 'description' => 'Secured and trusted means of accepting all payment methods supported worldwide. Novalnet provides the most convenient way to increase your sales and deliver seamless checkout experience for your customers.', + ], + ]; + + /** @var array */ + + private $customFields= [ + 'name' => 'novalnet', + 'config' => [ + 'label' => [ + 'en-GB' => 'Novalnet Comments', + 'de-DE' => 'Novalnet Comments', + ], + ], + 'customFields' => [ + [ + 'name' => 'novalnet_comments', + 'active' => true, + 'type' => CustomFieldTypes::TEXT, + 'config' => [ + 'componentName' => 'sw-field', + 'customFieldType' => 'text', + 'customFieldPosition' => 1, + 'label' => [ + 'en-GB' => 'Novalnet Coments', + 'de-DE' => 'Novalnet Kommentare', + ], + ], + ], + ], + 'relations' => [ + [ + 'entityName' => 'order_transaction', + ] + ] + ]; + + + private $customFieldsPaymentName= [ + 'name' => 'novalnetPaymentName', + 'config' => [ + 'label' => [ + 'en-GB' => 'Novalnet Payment', + 'de-DE' => 'Novalnet Payment', + ], + ], + 'customFields' => [ + [ + 'name' => 'novalnet_payment_name', + 'active' => true, + 'type' => CustomFieldTypes::TEXT, + 'config' => [ + 'componentName' => 'sw-field', + 'customFieldType' => 'text', + 'customFieldPosition' => 1, + 'label' => [ + 'en-GB' => 'Novalnet Payment', + 'de-DE' => 'Novalnet Zahlung', + ], + ], + ], + ], + 'relations' => [ + [ + 'entityName' => 'order_transaction', + ] + ] + ]; + + + /** @var array */ + + protected $defaultConfiguration = [ + 'emailMode' => true, + 'onHoldStatus' => 'authorized', + 'completeStatus' => 'paid' + ]; + + /** @var Context */ + private $context; + + /** @var ContainerInterface */ + + private $container; + + + /** @var EntityRepository */ + + private $mailTemplateRepo; + + /** @var EntityRepository */ + + private $mailTemplateTypeRepo; + + /** @constant string */ + + protected const SYSTEM_CONFIG_DOMAIN = 'NovalnetPayment.settings.'; + + + /** + * Constructs a `PaymentMethodInstaller` + * @param ContainerInterface $container + * @param Context $context + */ + public function __construct(ContainerInterface $container, Context $context) + { + $this->context = $context; + $this->container = $container; + $this->mailTemplateRepo = $this->container->get('mail_template.repository'); + $this->mailTemplateTypeRepo = $this->container->get('mail_template_type.repository'); + } + + /** + * Get payment name + * + * @param string $locale + * + * @return string + */ + public function getName(string $locale): string + { + if(!empty($this->translations[$locale]['name'])) { + return $this->translations[$locale]['name']; + } + return $this->translations['de-DE']['name']; + } + + /** + * Get payment description + * + * @param string $locale + * + * @return string + */ + public function getDescription(string $locale): string + { + if (!empty($this->translations[$locale]['name'])) { + return $this->translations[$locale]['description']; + } + return $this->translations['de-DE']['description']; + } + + /** Add Payment Methods on plugin installation */ + + public function install(): void + { + $this->addPaymentMethods(); + $this->createMailEvents(); + $this->alterPaymentTokenTable(); + } + + + /** Add Payment Methods on plugin update process */ + + public function update(): void + { + $this->addPaymentMethods(); + $this->updateMediaData(); + $this->alterPaymentTokenTable(); + } + + + /** Add payment logo into media on plugin activation */ + + public function activate(): void + { + $this->updateMediaData(); + } + + + /** Deactivate Payment Methods */ + + public function deactivate(): void + { + $this->deactivatePaymentMethods(); + } + + + /** Deactivate Payment Methods */ + + public function uninstall(): void + { + $this->deactivatePaymentMethods(); + } + + + /** Delete plugin related system configurations. */ + + public function removeConfiguration(): void + { + $systemConfigRepository = $this->container->get('system_config.repository'); + $criteria = (new Criteria()) + ->addFilter(new ContainsFilter('configurationKey', self::SYSTEM_CONFIG_DOMAIN)); + + $idSearchResult = $systemConfigRepository->searchIds($criteria, $this->context); + + $ids = array_map(static function ($id) { + return ['id' => $id]; + }, $idSearchResult->getIds()); + + $systemConfigRepository->delete($ids, $this->context); + } + + /** Add plugin related Payment Method.*/ + + private function addPaymentMethods(): void + { + $defaultLocale = (in_array($this->getDefaultLocaleCode(), ['de-DE', 'en-GB'])) ? $this->getDefaultLocaleCode() : 'en-GB'; + $context = $this->context; + $this->getPaymentMethodEntity(); + $paymentMethodId = $this->getPaymentMethods(); + + // Skip insertion if the payment already exists. + if (empty($paymentMethodId)) { + $translations = $this->translations; + $paymentMethodId = Uuid::randomHex(); + $paymentData = [ + [ + 'id' => $paymentMethodId, + 'name' => $this->getName($defaultLocale), + 'description' => $this->getDescription($defaultLocale), + 'position' => -1001, + 'handlerIdentifier' => NovalnetPayment::class, + 'translations' => $translations, + 'afterOrderEnabled' => true, + 'customFields' => [ + 'novalnet_payment_method_name' => 'novalnetpay', + ], + ] + ]; + + $this->context->scope(Context::SYSTEM_SCOPE, function (Context $context) use ($paymentData): void { + $this->container->get('payment_method.repository')->upsert($paymentData, $context); + }); + + $channels = $this->container->get('sales_channel.repository')->searchIds(new Criteria(), $this->context); + $salesChannelPaymentRepo = $this->container->get('sales_channel_payment_method.repository'); + + // Enable payment method on available channels. + if ($salesChannelPaymentRepo !=null) { + foreach ($channels->getIds() as $channel) { + $data = [ + 'salesChannelId' => $channel, + 'paymentMethodId' => $paymentMethodId, + ]; + $salesChannelPaymentRepo->upsert([$data], $this->context); + } + } + } + + $this->updateCustomFieldsForTranslations('novalnetpay', $paymentMethodId); + + // Set default configurations value for payment methods + $customFields = $this->customFields; + $customFieldExistsId = $this->customFieldsExist('novalnet'); + + if (!$customFieldExistsId && !empty($this->container->get('custom_field_set.repository'))) { + $this->container->get('custom_field_set.repository')->upsert([$customFields], $this->context); + } + + + $customFieldsPaymentName = $this->customFieldsPaymentName; + $customFieldsPaymentExistsId = $this->customFieldsExist('novalnetPaymentName'); + + if (!$customFieldsPaymentExistsId && !empty($this->container->get('custom_field_set.repository'))) { + $this->container->get('custom_field_set.repository')->upsert([$customFieldsPaymentName], $this->context); + } + + $systemConfig = $this->container->get(SystemConfigService::class); + + foreach ($this->defaultConfiguration as $key => $value) { + if (!empty($value)) { + $systemConfig->set(self::SYSTEM_CONFIG_DOMAIN . $key, $value); + } + } + + } + + + /** + * Get Novalnet Payment method instance + * + * @return string + */ + private function getPaymentMethods(): ?string + { + /** @var EntityRepository $paymentRepository */ + $paymentRepository = $this->container->get('payment_method.repository'); + + // Fetch ID for update + $paymentCriteria = (new Criteria())->addFilter(new EqualsFilter('handlerIdentifier', NovalnetPayment::class)); + $paymentmethodId = $paymentRepository->searchIds($paymentCriteria, $this->context)->firstId(); + + return $paymentmethodId; + } + + /** Deactivate Payment methods*/ + + private function deactivatePaymentMethods(): void + { + $paymentMethodId = $this->getPaymentMethods(); + + if (!$paymentMethodId) { + return; + } + + // Deactivate the payment methods. + $this->container->get('payment_method.repository')->update([ + [ + 'id' => $paymentMethodId, + 'active' => false, + ], + ], $this->context); + + } + + /** + * Get default langauge during plugin installation + * + * @return string|null + */ + private function getDefaultLocaleCode(): ?string + { + $criteria = new Criteria([Defaults::LANGUAGE_SYSTEM]); + $criteria->addAssociation('locale'); + + $systemDefaultLanguage = $this->container->get('language.repository')->search($criteria, $this->context)->first(); + + $locale = $systemDefaultLanguage->getLocale(); + if (!$locale) { + return null; + } + return $locale->getCode(); + } + + + /** + * Update payment custom fields + * + * @param string $paymentcode + * @param string $paymentMethodId + * + */ + private function updateCustomFieldsForTranslations(string $paymentcode, string $paymentMethodId): void + { + $customFields['novalnet_payment_method_name'] = $paymentcode; + + $customFields = json_encode($customFields, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + /** @var Connection $connection */ + $connection = $this->container->get(Connection::class); + + $connection->exec(sprintf(" + UPDATE `payment_method_translation` + SET + `custom_fields` = '%s' + WHERE + `custom_fields` IS NULL AND + `payment_method_id` = UNHEX('%s'); + ", $customFields, $paymentMethodId)); + } + + /** + * payment custom fields Exist + * + * @param string $customFieldName + + * @return string|null + */ + + private function customFieldsExist(string $customFieldName): ?string + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('name', $customFieldName)); + + if (empty($this->container->get('custom_field_set.repository'))) { + return null; + } + $result = $this->container->get('custom_field_set.repository')->searchIds($criteria, $this->context); + + if (!$result->getTotal()) { + return null; + } + + $customeFields = $result->getIds(); + return array_shift($customeFields); + } + + /** Delete plugin related mail configuration. */ + + public function deleteMailSettings(): void + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('mailTemplateType.technicalName', 'novalnet_order_confirmation_mail')); + + $mailData = $this->mailTemplateRepo->search($criteria, $this->context)->first(); + + if ($mailData) { + // delete subscription mail template + $this->mailTemplateRepo->delete([['id' => $mailData->getId()]], $this->context); + $this->mailTemplateTypeRepo->delete([['id' => $mailData->getMailTemplateTypeId()]], $this->context); + } + } + + /** + * Prepare and update media files on plugin activation + * + * @return void + */ + private function updateMediaData(): void + { + $paymentMethodId = $this->getPaymentMethods(); + + if (!$paymentMethodId) { + return; + } + $paymentMethodEntity = $this->getPaymentMethodMedia(); + /** @var MediaProvider|null $mediaProvider */ + $mediaProvider = $this->container->get(MediaProvider::class, ContainerInterface::NULL_ON_INVALID_REFERENCE); + + if (is_null($paymentMethodEntity->getMediaId())) { + $mediaId = $mediaProvider->getMediaId('novalnetpay', $this->context); + $this->container->get('payment_method.repository')->update([ + [ + 'id' => $paymentMethodId, + 'mediaId' => $mediaId, + ], + ], $this->context); + } + + } + + /** + * Create/Update novalnet order confirmation mail template + * + * @return void + */ + public function createMailEvents(): void + { + $mailType = $this->getMailTemplateType(); + $mailTemplateTypeId = Uuid::randomHex(); + $mailTemplateId = Uuid::randomHex(); + + if (!empty($mailType)) { + $mailTemplateId = $mailType->getId(); + $mailTemplateTypeId = $mailType->getMailTemplateTypeId(); + } + + $this->mailTemplateRepo->upsert([ + [ + 'id' => $mailTemplateId, + 'translations' => [ + 'de-DE' => [ + 'subject' => 'Bestellbestätigung', + 'contentHtml' => $this->getHtmlTemplateDe(), + 'contentPlain'=> $this->getPlainTemplateDe(), + 'description' => 'Novalnet Bestellbestätigung', + 'senderName' => '{{ salesChannel.name }}', + ], + 'en-GB' => [ + 'subject' => 'Order confirmation', + 'contentHtml' => $this->getHtmlTemplateEn(), + 'contentPlain'=> $this->getPlainTemplateEn(), + 'description' => 'Novalnet Order confirmation', + 'senderName' => '{{ salesChannel.name }}', + ], + ], + 'mailTemplateType' => [ + 'id' => $mailTemplateTypeId, + 'technicalName' => 'novalnet_order_confirmation_mail', + 'translations' => [ + 'de-DE' => [ + 'name' => 'Bestellbestätigung', + ], + 'en-GB' => [ + 'name' => 'Order Confirmation', + ], + ], + 'availableEntities' => [ + 'order' => 'order', + 'salesChannel' => 'sales_channel', + ], + ], + ] + ], $this->context); + } + + + /** Get Payment method entity */ + + private function getPaymentMethodEntity(): void + { + $criteria = new Criteria(); + $criteria->addFilter(new PrefixFilter('payment_method.handlerIdentifier', 'Novalnet\NovalnetPayment\Service')); + $paymentListed = $this->container->get('payment_method.repository')->search($criteria, $this->context); + $paymentLists = $paymentListed->getelements(); + + if (!empty($paymentLists)) { + foreach ($paymentLists as $payment) { + if ($payment->gethandlerIdentifier() != 'Novalnet\NovalnetPayment\Service\NovalnetPayment') { + if (!$payment->getId()) { + continue; + } + + // Deactivate the payment methods. + $this->container->get('payment_method.repository')->update([ + [ + 'id' => $payment->getId(), + 'active' => false, + ], + ], $this->context); + + $channels = $this->container->get('sales_channel.repository')->searchIds(new Criteria(), $this->context); + $getSalessChannels = $this->container->get('sales_channel_payment_method.repository'); + // Enable payment method on available channels. + if ($getSalessChannels !=null) { + foreach ($channels->getIds() as $channel) { + $data = [ + 'salesChannelId' => $channel, + 'paymentMethodId' => $payment->getId(), + ]; + $getSalessChannels->delete([$data], $this->context); + } + } + } + } + } + } + + /** + * Get Payment method entity + * + * + * @retrun PaymentMethodEntity|null + */ + private function getPaymentMethodMedia(): ?PaymentMethodEntity + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('handlerIdentifier', 'Novalnet\NovalnetPayment\Service\NovalnetPayment')); + + return $this->container->get('payment_method.repository')->search($criteria, $this->context)->first(); + } + + + + /** Alter Payment Token Table */ + + public function alterPaymentTokenTable(): void + { + /** @var Connection $connection */ + $connection = $this->container->get(Connection::class); + + $isTableExists = $connection->executeQuery(' + SELECT COUNT(*) as exists_tbl + FROM information_schema.tables + WHERE table_name = "novalnet_transaction_details" + AND table_schema = database()')->fetch(); + + if (!empty($isTableExists['exists_tbl'])) { + $isColumnExists = $connection->fetchOne('SHOW COLUMNS FROM `novalnet_transaction_details` LIKE "token_info"'); + + if (empty($isColumnExists)) { + $connection->exec(' + ALTER TABLE `novalnet_transaction_details` + ADD `token_info` varchar(255) DEFAULT NULL COMMENT "Transaction Token" AFTER `additional_details`; + '); + } + } + } + + /** + * Get English HTML Template + * + * @return string + */ + private function getHtmlTemplateEn(): string + { + return '
+ + {% set currencyIsoCode = order.currency.isoCode %} + {{order.orderCustomer.salutation.letterName }} {{order.orderCustomer.firstName}} {{order.orderCustomer.lastName}},
+
+ {% if instalment == false %} + Thank you for your order at {{ salesChannel.name }} (Number: {{order.orderNumber}}) on {{ order.orderDateTime|format_datetime("medium", "short", locale="en-GB") }}.
+ {% else %} + The next instalment cycle have arrived for the instalment order (OrderNumber: {{order.orderNumber}}) placed at the store {{ salesChannel.name }} on {{ order.orderDateTime|format_datetime("medium", "short", locale="en-GB") }}.
+ {% endif %} +
+ Information on your order:
+
+ + + + + + + + + + + {% for lineItem in order.lineItems|reverse %} + + + + + + + + {% endfor %} +
Pos.DescriptionQuantitiesPriceTotal
{{ loop.index }} + {{ lineItem.label|u.wordwrap(80) }}
+ {% if lineItem.payload.productNumber is defined %} Art. No.: {{ lineItem.payload.productNumber|u.wordwrap(80) }} {% endif %} +
{{ lineItem.quantity }}{{ lineItem.unitPrice|currency(currencyIsoCode) }}{{ lineItem.totalPrice|currency(currencyIsoCode) }}
+ + {% set delivery =order.deliveries.first %} + + + {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %} + {% set decimals = order.totalRounding.decimals %} + {% set total = order.price.totalPrice %} + {% if displayRounded %} + {% set total = order.price.rawTotal %} + {% set decimals = order.itemRounding.decimals %} + {% endif %} +

+
+
+ {% if delivery is not null %} + Shipping costs: {{order.deliveries.first.shippingCosts.totalPrice|currency(currencyIsoCode) }}
+ {% endif %} + Net total: {{ order.amountNet|currency(currencyIsoCode) }}
+ + {% for calculatedTax in order.price.calculatedTaxes %} + {% if order.taxStatus is same as(\'net\') %}plus{% else %}including{% endif %} {{ calculatedTax.taxRate }}% VAT. {{ calculatedTax.tax|currency(currencyIsoCode) }}
+ {% endfor %} + {% if not displayRounded %}{% endif %}Total gross: {{ order.amountTotal|currency(currencyIsoCode,decimals=decimals) }}{% if not displayRounded %}{% endif %}
+ {% if displayRounded %} + Rounded total gross: {{ order.price.totalPrice|currency(currencyIsoCode,decimals=order.totalRounding.decimals) }}
+ {% endif %} + +
+ + Selected payment type: {% if paymentName is not empty %} {{ paymentName }} {% else %} {{ order.transactions|last.paymentMethod.translated.name }} {% endif %}
+ {{ order.transactions|last.paymentMethod.translated.description }}
+
+ + Comments:
+ {{ note|replace({"/ ": "
"}) | raw }}
+
+ + {% if "INSTALMENT_INVOICE" in novalnetDetails.paymentType or "INSTALMENT_DIRECT_DEBIT_SEPA" in novalnetDetails.paymentType %} + {% if instalmentInfo is not empty %} + + + + + + + + + + + {% set count = 1 %} + {% set instalmentData = instalmentInfo.InstalmentDetails %} + {% for value in instalmentData %} + {% set amount = value.amount/100 %} + + + + + + + {% set count = count + 1 %} + {% endfor %} + +
S.NoNovalnet Transaction IDAmountNext Instalment Date
{{ count }}{{ instalmentData[count].reference ? instalmentData[count].reference : "-" }}{{ amount ? amount|currency(currencyIsoCode): "-" }}{{ instalmentData[count].cycleDate ? instalmentData[count].cycleDate|date("d/m/Y"): "-" }}
+
+ {% endif %} + {% endif %} + + {% if delivery is not null %} + Selected shipping type: {{ delivery.shippingMethod.translated.name }}
+ {{ delivery.shippingMethod.translated.description }}
+
+ {% endif %} + + {% set billingAddress = order.addresses.get(order.billingAddressId) %} + Billing address:
+ {{ billingAddress.company }}
+ {{ billingAddress.firstName }} {{ billingAddress.lastName }}
+ {{ billingAddress.street }}
+ {{ billingAddress.zipcode }} {{ billingAddress.city }}
+ {{ billingAddress.country.name }}
+
+ {% if delivery is not null %} + Shipping address:
+ {{ delivery.shippingOrderAddress.company }}
+ {{ delivery.shippingOrderAddress.firstName }} {{ delivery.shippingOrderAddress.lastName }}
+ {{ delivery.shippingOrderAddress.street }}
+ {{ delivery.shippingOrderAddress.zipcode}} {{ delivery.shippingOrderAddress.city }}
+ {{ delivery.shippingOrderAddress.country.name }}
+
+ {% endif %} + {% if billingAddress.vatId %} + + Your VAT-ID: {{ billingAddress.vatId }} + In case of a successful order and if you are based in one of the EU countries, you will receive your goods exempt from turnover tax.
+ {% endif %} +
+ You can check the current status of your order on our website under "My account" - "My orders" anytime: {{ rawUrl("frontend.account.order.single.page", { "deepLinkCode": order.deepLinkCode }, salesChannel.domains|first.url) }} +
+ If you have any questions, do not hesitate to contact us. + +

+
+
'; + } + + /** + * Get English Plain Template + * + * @return string + */ + private function getPlainTemplateEn(): string + { + return '{% set currencyIsoCode = order.currency.isoCode %} + {{ order.orderCustomer.salutation.letterName }} {{order.orderCustomer.firstName}} {{order.orderCustomer.lastName}}, + + {% if instalment == false %} + Thank you for your order at {{ salesChannel.name }} (Number: {{order.orderNumber}}) on {{ order.orderDateTime|format_datetime("medium", "short", locale="en-GB") }}. + {% else %} + The next instalment cycle have arrived for the instalment order (OrderNumber: {{order.orderNumber}}) placed at the store {{ salesChannel.name }} on {{ order.orderDateTime|format_datetime("medium", "short", locale="en-GB") }}. + {% endif %} + + Information on your order: + + Pos. Art.No. Description Quantities Price Total + + {% for lineItem in order.lineItems|reverse %} + {{ loop.index }} {% if lineItem.payload.productNumber is defined %}{{ lineItem.payload.productNumber|u.wordwrap(80) }}{% endif %} {{ lineItem.label|u.wordwrap(80) }} {{ lineItem.quantity }} {{ lineItem.unitPrice|currency(currencyIsoCode) }} {{ lineItem.totalPrice|currency(currencyIsoCode) }} + {% endfor %} + + {% set delivery = order.deliveries.first %} + + {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %} + {% set decimals = order.totalRounding.decimals %} + {% set total = order.price.totalPrice %} + {% if displayRounded %} + {% set total = order.price.rawTotal %} + {% set decimals = order.itemRounding.decimals %} + {% endif %} + + {% if delivery is not null %} + Shipping costs: {{order.deliveries.first.shippingCosts.totalPrice|currency(currencyIsoCode) }} + {% endif %} + Net total: {{ order.amountNet|currency(currencyIsoCode) }} + {% for calculatedTax in order.price.calculatedTaxes %} + {% if order.taxStatus is same as(\'net\') %}plus{% else %}including{% endif %} {{ calculatedTax.taxRate }}% VAT. {{ calculatedTax.tax|currency(currencyIsoCode) }}
+ {% endfor %} + Total gross: {{ order.amountTotal|currency(currencyIsoCode,decimals=decimals) }} + {% if displayRounded %} + Rounded total gross: {{ order.price.totalPrice|currency(currencyIsoCode,decimals=order.totalRounding.decimals) }} + {% endif %} + + Selected payment type: {% if paymentName is not empty %} {{ paymentName }} {% else %} {{ order.transactions|last.paymentMethod.translated.name }} {% endif %} + {{ order.transactions|last.paymentMethod.translated.description }} + + Comments: + {{ note|replace({"/ ": "
"}) | raw }} + + {% if "INSTALMENT_INVOICE" in novalnetDetails.paymentType or "INSTALMENT_DIRECT_DEBIT_SEPA" in novalnetDetails.paymentType %} + {% if instalmentInfo is not empty %} + S.No. Novalnet Transaction ID Amount Next Instalment Date + {% set count = 1 %} + {% set instalmentData = instalmentInfo.InstalmentDetails %} + {% for value in instalmentData %} + {%set amount = value.amount/100 %} + {{ count }} {{ instalmentData[count].reference ? instalmentData[count].reference : "-" }} {{ amount ? amount|currency(currencyIsoCode): "-" }} {{ instalmentData[count].cycleDate ? instalmentData[count].cycleDate|date("d/m/Y"): "-" }} + {% set count = count + 1 %} + {% endfor %} + {% endif %} + {% endif %} + + {% if delivery is not null %} + Selected shipping type: {{ delivery.shippingMethod.translated.name }} + {{ delivery.shippingMethod.translated.description }} + {% endif %} + + {% set billingAddress = order.addresses.get(order.billingAddressId) %} + Billing address: + {{ billingAddress.company }} + {{ billingAddress.firstName }} {{ billingAddress.lastName }} + {{ billingAddress.street }} + {{ billingAddress.zipcode }} {{ billingAddress.city }} + {{ billingAddress.country.name }} + + {% if delivery is not null %} + Shipping address: + {{ delivery.shippingOrderAddress.company }} + {{ delivery.shippingOrderAddress.firstName }} {{ delivery.shippingOrderAddress.lastName }} + {{ delivery.shippingOrderAddress.street }} + {{ delivery.shippingOrderAddress.zipcode}} {{ delivery.shippingOrderAddress.city }} + {{ delivery.shippingOrderAddress.country.name }} + + {% endif %} + + {% if billingAddress.vatId %} + Your VAT-ID: {{ billingAddress.vatId }} + In case of a successful order and if you are based in one of the EU countries, you will receive your goods exempt from turnover tax. + {% endif %} + + You can check the current status of your order on our website under "My account" - "My orders" anytime: {{ rawUrl("frontend.account.order.single.page", { "deepLinkCode": order.deepLinkCode }, salesChannel.domains|first.url) }} + If you have any questions, do not hesitate to contact us.'; + + } + + /** + * Get German HTML Template + * + * @return string + */ + private function getHtmlTemplateDe(): string + { + return '
+ + {% set currencyIsoCode = order.currency.isoCode %} + {{order.orderCustomer.salutation.letterName }} {{order.orderCustomer.firstName}} {{order.orderCustomer.lastName}},
+
+ {% if instalment == false %} + vielen Dank für Ihre Bestellung im {{ salesChannel.name }} (Nummer: {{order.orderNumber}}) am {{ order.orderDateTime|format_datetime("medium", "short", locale="de-DE") }}.
+ {% else %} + Für Ihre (Bestellung Nr: {{order.orderNumber}}) bei {{ salesChannel.name }}, ist die nächste Rate fällig. Bitte beachten Sie weitere Details unten am {{ order.orderDateTime|format_datetime("medium", "short", locale="de-DE") }}.
+ {% endif %} +
+ Informationen zu Ihrer Bestellung:
+
+ + + + + + + + + + + {% for lineItem in order.lineItems |reverse %} + + + + + + + + {% endfor %} +
Pos.BezeichnungMengePreisSumme
{{ loop.index }} + {{ lineItem.label|u.wordwrap(80) }}
+ {% if lineItem.payload.productNumber is defined %} Artikel-Nr: {{ lineItem.payload.productNumber|u.wordwrap(80) }} {% endif %} +
{{ lineItem.quantity }}{{ lineItem.unitPrice|currency(currencyIsoCode) }}{{ lineItem.totalPrice|currency(currencyIsoCode) }}
+ + {% set delivery =order.deliveries.first %} + + {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %} + {% set decimals = order.totalRounding.decimals %} + {% set total = order.price.totalPrice %} + {% if displayRounded %} + {% set total = order.price.rawTotal %} + {% set decimals = order.itemRounding.decimals %} + {% endif %} +

+
+
+ {% if delivery is not null %} + Versandkosten: {{order.deliveries.first.shippingCosts.totalPrice|currency(currencyIsoCode) }}
+ {% endif %} + Gesamtkosten Netto: {{ order.amountNet|currency(currencyIsoCode) }}
+ {% for calculatedTax in order.price.calculatedTaxes %} + {% if order.taxStatus is same as(\'net\') %}zzgl{% else %}inkl{% endif %} {{ calculatedTax.taxRate }}% MWST. {{ calculatedTax.tax|currency(currencyIsoCode) }}
+ {% endfor %} + {% if not displayRounded %}{% endif %}Gesamtkosten Brutto: {{ order.amountTotal|currency(currencyIsoCode,decimals=decimals) }}{% if not displayRounded %}{% endif %}
+ {% if displayRounded %} + Gesamtkosten Brutto gerundet: {{ order.price.totalPrice|currency(currencyIsoCode,decimals=order.totalRounding.decimals) }}
+ {% endif %} +
+ Gewählte Zahlungsart: {% if paymentName is not empty %} {{ paymentName }} {% else %} {{ order.transactions|last.paymentMethod.translated.name }} {% endif %}
+ {{ order.transactions|last.paymentMethod.translated.description }}
+
+ + Kommentare:
+ {{ note|replace({"/ ": "
"}) | raw }}
+
+ + {% if "INSTALMENT_INVOICE" in novalnetDetails.paymentType or "INSTALMENT_DIRECT_DEBIT_SEPA" in novalnetDetails.paymentType %} + {% if instalmentInfo is not empty %} + + + + + + + + + + + {% set count = 1 %} + {% set instalmentData = instalmentInfo.InstalmentDetails %} + {% for value in instalmentData %} + {% set amount = value.amount/100 %} + + + + + + + {% set count = count + 1 %} + {% endfor %} + +
S.NrNovalnet-Transaktions-IDBetragNächste Rate fällig am
{{ count }}{{ instalmentData[count].reference ? instalmentData[count].reference : "-" }}{{ amount ? amount|currency(currencyIsoCode): "-" }}{{ instalmentData[count].cycleDate ? instalmentData[count].cycleDate|date("d/m/Y"): "-" }}
+
+ {% endif %} + {% endif %} + + {% if delivery is not null %} + Gewählte Versandtart: {{ delivery.shippingMethod.translated.name }}
+ {{ delivery.shippingMethod.translated.description }}
+
+ {% endif %} + + {% set billingAddress = order.addresses.get(order.billingAddressId) %} + Rechnungsaddresse:
+ {{ billingAddress.company }}
+ {{ billingAddress.firstName }} {{ billingAddress.lastName }}
+ {{ billingAddress.street }}
+ {{ billingAddress.zipcode }} {{ billingAddress.city }}
+ {{ billingAddress.country.name }}
+
+ + {% if delivery is not null %} + Lieferadresse:
+ {{ delivery.shippingOrderAddress.company }}
+ {{ delivery.shippingOrderAddress.firstName }} {{ delivery.shippingOrderAddress.lastName }}
+ {{ delivery.shippingOrderAddress.street }}
+ {{ delivery.shippingOrderAddress.zipcode}} {{ delivery.shippingOrderAddress.city }}
+ {{ delivery.shippingOrderAddress.country.name }}
+
+ + {% endif %} + + {% if billingAddress.vatId %} + Ihre Umsatzsteuer-ID: {{ billingAddress.vatId }} + Bei erfolgreicher Prüfung und sofern Sie aus dem EU-Ausland + bestellen, erhalten Sie Ihre Ware umsatzsteuerbefreit.
+ {% endif %} +
+
+ Den aktuellen Status Ihrer Bestellung können Sie auch jederzeit auf unserer Webseite im Bereich "Mein Konto" - "Meine Bestellungen" abrufen: {{ rawUrl("frontend.account.order.single.page", { "deepLinkCode": order.deepLinkCode }, salesChannel.domains|first.url) }} +
+ Für Rückfragen stehen wir Ihnen jederzeit gerne zur Verfügung. + +

+
+
'; + } + + /** + * Get German Plain Template + * + * @return string + */ + private function getPlainTemplateDe(): string + { + return '{% set currencyIsoCode = order.currency.isoCode %} + {{order.orderCustomer.salutation.letterName }} {{order.orderCustomer.firstName}} {{order.orderCustomer.lastName}}, + + {% if instalment == false %} + vielen Dank für Ihre Bestellung im {{ salesChannel.name }} (Nummer: {{order.orderNumber}}) am {{ order.orderDateTime|format_datetime("medium", "short", locale="de-DE") }}. + {% else %} + Für Ihre (Bestellung Nr: {{order.orderNumber}}) bei {{ salesChannel.name }}, ist die nächste Rate fällig. Bitte beachten Sie weitere Details unten am {{ order.orderDateTime|format_datetime("medium", "short", locale="de-DE")}}. + {% endif %} + + Informationen zu Ihrer Bestellung: + + Pos. Artikel-Nr. Beschreibung Menge Preis Summe + {% for lineItem in order.lineItems |reverse %} + {{ loop.index }} {% if lineItem.payload.productNumber is defined %}{{ lineItem.payload.productNumber|u.wordwrap(80) }}{% endif %} {{ lineItem.label|u.wordwrap(80) }} {{ lineItem.quantity }} {{ lineItem.unitPrice|currency(currencyIsoCode) }} {{ lineItem.totalPrice|currency(currencyIsoCode) }} + {% endfor %} + + {% set delivery =order.deliveries.first %} + + {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %} + {% set decimals = order.totalRounding.decimals %} + {% set total = order.price.totalPrice %} + {% if displayRounded %} + {% set total = order.price.rawTotal %} + {% set decimals = order.itemRounding.decimals %} + {% endif %} + + {% if delivery is not null %} + Versandtkosten: {{order.deliveries.first.shippingCosts.totalPrice|currency(currencyIsoCode) }} + {% endif %} + Gesamtkosten Netto: {{ order.amountNet|currency(currencyIsoCode) }} + {% for calculatedTax in order.price.calculatedTaxes %} + {% if order.taxStatus is same as(\'net\') %}zzgl{% else %}inkl{% endif %} {{ calculatedTax.taxRate }}% MWST. {{ calculatedTax.tax|currency(currencyIsoCode) }}
+ {% endfor %} + Gesamtkosten Brutto: {{ order.amountTotal|currency(currencyIsoCode,decimals=decimals) }} + {% if displayRounded %} + Gesamtkosten Brutto gerundet: {{ order.price.totalPrice|currency(currencyIsoCode,decimals=order.totalRounding.decimals) }} + {% endif %} + + Gewählte Zahlungsart: {% if paymentName is not empty %} {{ paymentName }} {% else %} {{ order.transactions|last.paymentMethod.translated.name }} {% endif %} + {{ order.transactions|last.paymentMethod.translated.description }} + + Kommentare: + {{ note|replace({"/ ": "
"}) | raw }} + + {% if "INSTALMENT_INVOICE" in novalnetDetails.paymentType or "INSTALMENT_DIRECT_DEBIT_SEPA" in novalnetDetails.paymentType %} + {% if instalmentInfo is not empty %} + S.Nr Novalnet-Transaktions-ID Betrag Nächste Rate fällig am + {% set count = 1 %} + {% set instalmentData = instalmentInfo.InstalmentDetails %} + {% for value in instalmentData %} + {%set amount = value.amount/100 %} + {{ count }} {{ instalmentData[count].reference ? instalmentData[count].reference : "-" }} {{ amount ? amount|currency(currencyIsoCode): "-" }} {{ instalmentData[count].cycleDate ? instalmentData[count].cycleDate|date("d/m/Y"): "-" }} + {% set count = count + 1 %} + {% endfor %} + {% endif %} + {% endif %} + + {% if delivery is not null %} + Gewählte Versandtart: {{ delivery.shippingMethod.translated.name }} + {{ delivery.shippingMethod.translated.description }} + {% endif %} + + {% set billingAddress = order.addresses.get(order.billingAddressId) %} + Rechnungsadresse: + {{ billingAddress.company }} + {{ billingAddress.firstName }} {{ billingAddress.lastName }} + {{ billingAddress.street }} + {{ billingAddress.zipcode }} {{ billingAddress.city }} + {{ billingAddress.country.name }} + + {% if delivery is not null %} + Lieferadresse: + {{ delivery.shippingOrderAddress.company }} + {{ delivery.shippingOrderAddress.firstName }} {{ delivery.shippingOrderAddress.lastName }} + {{ delivery.shippingOrderAddress.street }} + {{ delivery.shippingOrderAddress.zipcode}} {{ delivery.shippingOrderAddress.city }} + {{ delivery.shippingOrderAddress.country.name }} + + {% endif %} + + {% if billingAddress.vatId %} + Ihre Umsatzsteuer-ID: {{ billingAddress.vatId }} + Bei erfolgreicher Prüfung und sofern Sie aus dem EU-Ausland + bestellen, erhalten Sie Ihre Ware umsatzsteuerbefreit. + {% endif %} + + Den aktuellen Status Ihrer Bestellung können Sie auch jederzeit auf unserer Webseite im Bereich "Mein Konto" - "Meine Bestellungen" abrufen: {{ rawUrl("frontend.account.order.single.page", { "deepLinkCode": order.deepLinkCode }, salesChannel.domains|first.url) }} + Für Rückfragen stehen wir Ihnen jederzeit gerne zur Verfügung.'; + } + + /** + * Get Mail template entity + * + * @return MailTemplateEntity|null + */ + private function getMailTemplateType(): ?MailTemplateEntity + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('mailTemplateType.technicalName', 'novalnet_order_confirmation_mail')); + + /** @var MailTemplateEntity|null */ + return $this->mailTemplateRepo->search($criteria, $this->context)->first(); + } +} diff --git a/src/Migration/Migration1678945880AddNovalnetPaymentTable.php b/src/Migration/Migration1678945880AddNovalnetPaymentTable.php new file mode 100644 index 0000000..7dff95b --- /dev/null +++ b/src/Migration/Migration1678945880AddNovalnetPaymentTable.php @@ -0,0 +1,44 @@ +executeUpdate(' + CREATE TABLE IF NOT EXISTS `novalnet_transaction_details` ( + `id` binary(16) NOT NULL, + `tid` BIGINT(20) UNSIGNED DEFAULT NULL COMMENT "Novalnet Transaction Reference ID", + `payment_type` VARCHAR(50) DEFAULT NULL COMMENT "Executed Payment type of this order", + `amount` INT(11) UNSIGNED DEFAULT 0 COMMENT "Transaction amount", + `currency` VARCHAR(11) DEFAULT NULL COMMENT "Transaction currency", + `paid_amount` INT(11) UNSIGNED DEFAULT 0 COMMENT "Paid amount", + `refunded_amount` INT(11) UNSIGNED DEFAULT 0 COMMENT "Refunded amount", + `gateway_status` VARCHAR(30) DEFAULT NULL COMMENT "Novalnet transaction status", + `order_no` VARCHAR(64) DEFAULT NULL COMMENT "Order ID from shop", + `customer_no` VARCHAR(255) COMMENT "Customer Number from shop", + `additional_details` LONGTEXT DEFAULT NULL COMMENT "Additional details", + `token_info` VARCHAR(255) DEFAULT NULL COMMENT "Transaction Token", + `created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT "Created date", + `updated_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT "Updated date", + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT="Novalnet Transaction History" + '); + } + + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/NovalnetPayment.php b/src/NovalnetPayment.php new file mode 100644 index 0000000..b0ef8f1 --- /dev/null +++ b/src/NovalnetPayment.php @@ -0,0 +1,102 @@ +container, $installContext->getContext()))->install(); + parent::install($installContext); + } + + /** + * Plugin update process + * + * @param UpdateContext $updateContext + */ + public function update(UpdateContext $updateContext): void + { + (new PaymentMethodInstaller($this->container, $updateContext->getContext()))->update(); + parent::update($updateContext); + } + + /** + * Plugin uninstall process + * + * @param UninstallContext $uninstallContext + */ + public function uninstall(UninstallContext $uninstallContext): void + { + $uninstaller = new PaymentMethodInstaller($this->container, $uninstallContext->getContext()); + $uninstaller->uninstall(); + + + if (!$uninstallContext->keepUserData()) { + $uninstaller->removeConfiguration(); + $uninstaller->deleteMailSettings(); + } + parent::uninstall($uninstallContext); + } + + /** + * Plugin activate process + * + * @param ActivateContext $activateContext + */ + public function activate(ActivateContext $activateContext): void + { + (new PaymentMethodInstaller($this->container, $activateContext->getContext()))->activate(); + parent::activate($activateContext); + } + + /** + * Plugin deactivate process + * + * @param DeactivateContext $deactivateContext + */ + public function deactivate(DeactivateContext $deactivateContext): void + { + (new PaymentMethodInstaller($this->container, $deactivateContext->getContext()))->deactivate(); + parent::deactivate($deactivateContext); + } +} diff --git a/src/Resources/app/administration/src/acl/index.js b/src/Resources/app/administration/src/acl/index.js new file mode 100644 index 0000000..c2d0d8e --- /dev/null +++ b/src/Resources/app/administration/src/acl/index.js @@ -0,0 +1,67 @@ +Shopware.Service('privileges') + .addPrivilegeMappingEntry({ + category: 'permissions', + parent: 'orders', + key: 'novalnet_extension', + roles: { + viewer: { + privileges: [ + 'novalnet_transaction_details:read', + ], + dependencies: [], + }, + editor: { + privileges: [ + 'novalnet_transaction_details:update', + 'order_transaction:read', + 'order_transaction:update', + ], + dependencies: [ + 'novalnet_extension.viewer', + 'order.editor', + ], + }, + creator: { + privileges: [ + 'novalnet_transaction_details:create', + ], + dependencies: [ + 'novalnet_extension.viewer', + 'novalnet_extension.editor', + ], + }, + deleter: { + privileges: [ + 'novalnet_transaction_details:delete', + ], + dependencies: [ + 'novalnet_extension.viewer', + ], + }, + }, + }); +Shopware.Service('privileges').addPrivilegeMappingEntry({ + category: 'permissions', + parent: 'novalnet_payment', + key: 'novalnet_payment', + roles: { + viewer: { + privileges: [ + 'system_config:read', + 'sales_channel:read', + ], + dependencies: [], + }, + editor: { + privileges: [ + 'system_config:update', + 'system_config:create', + 'system_config:delete', + 'sales_channel:update', + ], + dependencies: [ + 'novalnet_payment.viewer', + ], + }, + }, +}); diff --git a/src/Resources/app/administration/src/core/service/api/novalnet-payment-api-credentials.service.js b/src/Resources/app/administration/src/core/service/api/novalnet-payment-api-credentials.service.js new file mode 100644 index 0000000..8b99a03 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/novalnet-payment-api-credentials.service.js @@ -0,0 +1,197 @@ +const ApiService = Shopware.Classes.ApiService; + +class NovalPaymentApiCredentialsService extends ApiService { + constructor(httpClient, loginService, apiEndpoint = 'novalnet-payment') { + super(httpClient, loginService, apiEndpoint); + } + + validateApiCredentials(clientId, accessKey) { + const headers = this.getBasicHeaders(); + + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/validate-api-credentials`, + { + clientId, + accessKey + }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + getNovalnetAmount(orderNumber){ + const headers = this.getBasicHeaders(); + + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/transaction-amount`, + { + orderNumber + }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + refundPayment(orderNumber, refundAmount, reason, instalmentCycleTid){ + const headers = this.getBasicHeaders(); + + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/refund-amount`, + { + orderNumber, + refundAmount, + reason, + instalmentCycleTid + }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + managePayment(orderNumber, status){ + const headers = this.getBasicHeaders(); + + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/manage-payment`, + { + orderNumber: orderNumber, + status: status + }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + instalmentCancel(orderNumber, cancelType){ + const headers = this.getBasicHeaders(); + + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/instalment-cancel`, + { + orderNumber, + cancelType + }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + + BookOrderAmount(orderNumber, bookAmount) { + const apiRoute = `_action/${this.getApiBasePath()}/book-amount`; + + return this.httpClient.post( + apiRoute, + { + orderNumber, + bookAmount + }, + { + headers: this.getBasicHeaders() + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + getNovalnetPaymentMethod (orderNumber) { + const apiRoute = `_action/${this.getApiBasePath()}/novalnet-paymentmethod`; + + return this.httpClient.post( + apiRoute, + { + orderNumber + + }, + { + headers: this.getBasicHeaders() + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + configureWebhookUrl(url, productActivationKey, paymentAccessKey) { + const apiRoute = `_action/${this.getApiBasePath()}/webhook-url-configure`; + + return this.httpClient.post( + apiRoute, + { + url, + productActivationKey, + paymentAccessKey + }, + { + headers: this.getBasicHeaders() + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + novalnetPayment(shippingaddress, billingaddress, amount, currency, customer) { + const apiRoute = `_action/${this.getApiBasePath()}/load-payment-form`; + + return this.httpClient.post( + apiRoute, + { + shippingaddress, + billingaddress, + amount, + currency, + customer + }, + { + headers: this.getBasicHeaders() + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + + paymentValue(value, customer) { + const apiRoute = `_action/${this.getApiBasePath()}/payment-value-data`; + + return this.httpClient.post( + apiRoute, + { + value, + customer + }, + { + headers: this.getBasicHeaders() + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + +} + +export default NovalPaymentApiCredentialsService; diff --git a/src/Resources/app/administration/src/init/api-service.init.js b/src/Resources/app/administration/src/init/api-service.init.js new file mode 100644 index 0000000..d540c7e --- /dev/null +++ b/src/Resources/app/administration/src/init/api-service.init.js @@ -0,0 +1,11 @@ +import NovalPaymentApiCredentialsService + from '../../src/core/service/api/novalnet-payment-api-credentials.service'; + +const { Application } = Shopware; + +Application.addServiceProvider('NovalPaymentApiCredentialsService', (container) => { + const initContainer = Application.getContainer('init'); + + return new NovalPaymentApiCredentialsService(initContainer.httpClient, container.loginService); +}); + diff --git a/src/Resources/app/administration/src/main.js b/src/Resources/app/administration/src/main.js new file mode 100644 index 0000000..dcbc5ea --- /dev/null +++ b/src/Resources/app/administration/src/main.js @@ -0,0 +1,3 @@ +import './module/novalnet-payment'; +import './init/api-service.init'; +import './acl'; diff --git a/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/index.js b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/index.js new file mode 100644 index 0000000..c835a80 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/index.js @@ -0,0 +1,299 @@ +import template from './novalnet-payment-credentials.html.twig'; +import './novalnet-payment-credentials.scss'; + +const { Component, Mixin } = Shopware; +const { Criteria } = Shopware.Data; +const { object, types } = Shopware.Utils; + +Component.register('novalnet-payment-credentials', { + template, + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + name: 'NovalnetPaymentCredentials', + icon: 'default-action-settings', + + props: { + actualConfigData: { + type: Object, + required: true + }, + allConfigs: { + type: Object, + required: true + }, + selectedSalesChannelId: { + required: true + } + }, + + data() { + const url = window.location .protocol + "//" + window.location.host + window.location.pathname; + const generatedUrl = url.split("/admin").join(""); + return { + onHoldOptions:[], + completeOptions:[], + onhold: 'authorized', + completed: 'paid', + shouldDisable :false, + projectMode: false, + apiActivationKey: '', + paymentAccessKey: '', + isLoading: false, + isRequested : '', + showMessage: false, + buttonLoad: false, + tariffId: '', + tariffOptions: [], + NovalnetPaymentCallBackUrl : generatedUrl + "/novalnet/callback" + + }; + }, + + inject: [ + 'repositoryFactory', + 'NovalPaymentApiCredentialsService', + 'systemConfigApiService', + 'acl', + ], + + watch: { + actualConfigData: { + handler(configData) { + + if (!configData) { + return; + } + this.$emit('update:value', configData); + }, + deep: true + } + }, + + computed: { + actualConfigData: { + get() { + return this.allConfigs[this.selectedSalesChannelId]; + }, + set(config) { + this.allConfigs = { + ...this.allConfigs, + [this.selectedSalesChannelId]: config + }; + } + }, + }, + + created() { + this.createdComponent(); + }, + + updated() { + this.createdComponent(); + }, + + methods: { + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkBoolFieldInheritance(value) { + return typeof value !== 'boolean'; + }, + + getInheritValue(key) { + if (this.selectedSalesChannelId == null ) { + return this.actualConfigData[key]; + } else { + return this.allConfigs['null'][key]; + } + }, + + onCheckApi() { + if(event.target.name === 'NovalnetPayment.settings.clientId') { + this.apiActivationKey = this.actualConfigData['NovalnetPayment.settings.clientId'] = event.target.value; + } else if(event.target.name === 'NovalnetPayment.settings.accessKey') { + this.paymentAccessKey = this.actualConfigData['NovalnetPayment.settings.accessKey'] = event.target.value; + } + + if (this.apiActivationKey === '' && this.paymentAccessKey === '') + { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.apiFailureMessage') + }); + return; + } + this.isRequested = ''; + this.showMessage = true; + this.createdComponent(); + }, + + createdComponent() { + const me = this; + if(this.actualConfigData !== undefined && this.isRequested !== this.selectedSalesChannelId) + { + this.isRequested = this.selectedSalesChannelId; + this.apiActivationKey = this.actualConfigData['NovalnetPayment.settings.clientId'] || this.allConfigs.null['NovalnetPayment.settings.clientId']; + this.paymentAccessKey = (this.actualConfigData['NovalnetPayment.settings.accessKey'] || this.allConfigs.null['NovalnetPayment.settings.accessKey']); + + if(this.apiActivationKey !== undefined && this.apiActivationKey !== '' && this.paymentAccessKey !== undefined && this.paymentAccessKey !== '' ) + { + this.apiActivationKey = this.apiActivationKey.replace(/\s/g, ""); + this.paymentAccessKey = this.paymentAccessKey.replace(/\s/g, ""); + this.isLoading = true; + this.NovalPaymentApiCredentialsService.validateApiCredentials(this.apiActivationKey, this.paymentAccessKey).then((response) => { + const status = response.serverResponse.result.status_code; + this.isLoading = false; + if(status !== 100) + { + if(this.showMessage === true) + { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: response.serverResponse.result.status_text, + autoClose: true + }); + } + this.showMessage = false; + + } else { + me.tariffOptions = []; + response.tariffResponse.forEach(((tariff) => { + + this.actualConfigData['NovalnetPayment.settings.clientKey'] = response.serverResponse.merchant.client_key; + + me.tariffOptions.push({ + value: tariff.id, + label: tariff.name + }); + + if(this.tariffId === undefined || this.tariffId === '') + { + this.tariffId = {value: tariff.id, label: tariff.name}; + } + + if(this.showMessage === true) + { + this.createNotificationSuccess({ + title: this.$tc('novalnet-payment.settingForm.titleSuccess'), + message: this.$tc('novalnet-payment.settingForm.successMessage'), + autoClose: true + }); + } + + this.showMessage = false; + if(response.serverResponse.merchant.test_mode === 1) + { + this.projectMode = true; + } + })); + + } + }).catch((errorResponse) => { + this.isLoading = false; + }); + } + } + + this.onHoldOptions = [ + { + value: 'open', + label: this.$tc('novalnet-payment.onhold.open') + }, + { + value: 'process', + label: this.$tc('novalnet-payment.onhold.process') + }, + { + value: 'authorized', + label: this.$tc('novalnet-payment.onhold.authorized') + }, + { + value: 'cancel', + label: this.$tc('novalnet-payment.onhold.cancel') + }, + { + value: 'failed', + label: this.$tc('novalnet-payment.onhold.failed') + }]; + + this.completeOptions = [ + + { + value: 'paid', + label: this.$tc('novalnet-payment.onhold.paid') + }, + { + value: 'paidPartially', + label: this.$tc('novalnet-payment.onhold.paidPartially') + }, + { + value: 'cancel', + label: this.$tc('novalnet-payment.onhold.cancel') + }, + { + value: 'failed', + label: this.$tc('novalnet-payment.onhold.failed') + } + ]; + }, + + configureWebhookUrl() { + const productActivationKey = this.actualConfigData['NovalnetPayment.settings.clientId'] || this.allConfigs.null['NovalnetPayment.settings.clientId']; + const accessKey = this.actualConfigData['NovalnetPayment.settings.accessKey'] || this.allConfigs.null['NovalnetPayment.settings.accessKey']; + + if ( productActivationKey === undefined || productActivationKey === '' || accessKey === undefined || accessKey === '') + { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.apiFailureMessage') + }); + + return; + } + + if( this.NovalnetPaymentCallBackUrl) + { + if (/^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/.*)?$/i.test(this.NovalnetPaymentCallBackUrl) === false) + { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.webhookUrlFailure') + }); + return false; + } + + this.buttonLoad = true; + + this.NovalPaymentApiCredentialsService.configureWebhookUrl(this.NovalnetPaymentCallBackUrl, productActivationKey, accessKey).then((response) => { + + if(response.result.status !== undefined && response.result.status != null && response.result.status !== '' && response.result.status === 'SUCCESS') + { + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.webhookUrlSuccess') + }); + } else if(response.result.status_text !== undefined && response.result.status_text != null && response.result.status_text !== '') { + this.createNotificationError({ + message: response.result.status_text, + }); + } else { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.webhookUrlFailure') + }); + } + this.buttonLoad = false; + + }).catch((errorResponse) => { + this.buttonLoad = false; + }); + } + } + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.html.twig new file mode 100644 index 0000000..7daf585 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.html.twig @@ -0,0 +1,202 @@ +{% block novalnet_payment_content_card_channel_config_credentials %} + + + {% block novalnet_payment_content_card_channel_config_credentials_card_container %} + + + {% block novalnet_payment_content_card_channel_config_credentials_card_container_settings %} +
+ + + {{ $tc('novalnet-payment.module.projectInfo') }} + + +
+
+
+
+ + + {% block novalnet_payment_content_card_channel_config_credentials_card_container_settings_client_id %} + + + + + + + {% endblock %} + + {% block novalnet_payment_content_card_channel_config_credentials_card_container_settings_break %} +
+ {% endblock %} + + {% block novalnet_payment_content_card_channel_config_credentials_card_container_settings_client_secret %} + + + + + + + + + + + + + + + + + + + {% endblock %} +
+ {% endblock %} +
+ {% endblock %} + + {% block novalnet_payment_content_card_channel_loading %} + + {% endblock %} +
+{% endblock %} + +{% block novalnet_payment_content_card_channel_config_merchant_credentials %} + + + {% block novalnet_payment_content_card_channel_config_merchant_credentials_card_container %} + + + {% block novalnet_payment_content_card_channel_config_merchant_credentials_card_container_settings %} +
+ + {% block novalnet_payment_content_card_channel_config_merchant_credentials_card_container_settings_ip %} + + + + + {{ $tc('novalnet-payment.settingForm.merchantSettings.callbackUrl.button') }} + + + + + + + + {% endblock %} +
+ {% endblock %} +
+ {% endblock %} +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.scss b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.scss new file mode 100644 index 0000000..b60743e --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-credentials/novalnet-payment-credentials.scss @@ -0,0 +1,8 @@ +.novalnet_payment-settings-project{ + font-size: 14px; + margin-bottom: 8px; +} + +.novalnet-payment-callback-field{ + margin-bottom: 15px; +} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/index.js b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/index.js new file mode 100644 index 0000000..79f3f3e --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/index.js @@ -0,0 +1,13 @@ +import template from './novalnet-payment-settings-icon.html.twig'; + +const { Component } = Shopware; + +Component.register('novalnet-payment-settings-icon', { + template, + + computed: { + assetFilter() { + return Shopware.Filter.getByName('asset'); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/novalnet-payment-settings-icon.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/novalnet-payment-settings-icon.html.twig new file mode 100644 index 0000000..4ab5ebf --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/components/novalnet-payment-settings-icon/novalnet-payment-settings-icon.html.twig @@ -0,0 +1,3 @@ +{% block novalnet_payment_settings_icon %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/index.js new file mode 100644 index 0000000..6722df9 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/index.js @@ -0,0 +1,84 @@ +import template from './sw-order-general-info.html.twig'; + + +/** + * @package customer-order + */ + +const { Component, Mixin } = Shopware; +const { Criteria } = Shopware.Data; + + +Component.override('sw-order-general-info', { + template, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + ], + + mixins: [ + Mixin.getByName('notification'), + ], + + props: { + order: { + type: Object, + required: true, + }, + }, + + data() { + return { + paymentMethod : '' + }; + }, + + watch: { + + order: { + deep: true, + handler() { + if (this.order == '') { + return; + } + + if (this.order.transactions.last().paymentMethod.customFields != null && this.order.transactions.last().paymentMethod.customFields.novalnet_payment_method_name != undefined && this.order.transactions.last().paymentMethod.customFields.novalnet_payment_method_name == "novalnetpay") { + + if(this.order.transactions.last().customFields != null && this.order.transactions.last().customFields.novalnet_payment_name != undefined && this.order.transactions.last().customFields.novalnet_payment_name !=''){ + this.paymentMethod = this.order.transactions.last().customFields.novalnet_payment_name; + } else { + + this.NovalPaymentApiCredentialsService.getNovalnetPaymentMethod(this.order.orderNumber).then((payment) => { + if(payment != undefined && payment != null) + { + if(payment.paymentName != undefined && payment.paymentName != null){ + this.paymentMethod = payment.paymentName; + } + else { + this.paymentMethod = this.order.transactions.last().paymentMethod.translated.distinguishableName; + } + } + else { + this.paymentMethod = this.order.transactions.last().paymentMethod.translated.distinguishableName; + } + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}` + }); + }); + } + } + else { + + this.paymentMethod = this.order.transactions.last().paymentMethod.translated.distinguishableName; + } + }, + immediate: true + } + } + + + + +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/sw-order-general-info.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/sw-order-general-info.html.twig new file mode 100644 index 0000000..cef9332 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-general-info/sw-order-general-info.html.twig @@ -0,0 +1,18 @@ +{% block sw_order_detail_base_general_info_summary_sub_description %} +
+ {{ $tc('sw-order.generalTab.info.summary.on') }} + {{ dateFilter(order.orderDateTime, { + hour: '2-digit', + minute: '2-digit', + day: '2-digit', + month: '2-digit', + year: 'numeric' + }) }} + {{ $tc('sw-order.generalTab.info.summary.with') }} + {{ paymentMethod }} + +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/index.js new file mode 100644 index 0000000..6a9c365 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/index.js @@ -0,0 +1,83 @@ +import template from './sw-order-user-card.html.twig'; + + +/** + * @package customer-order + */ + +const { Component, Mixin } = Shopware; +const { Criteria } = Shopware.Data; + + +Component.override('sw-order-user-card', { + template, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + ], + + mixins: [ + Mixin.getByName('notification'), + ], + + props: { + currentOrder: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: true, + } + }, + + data() { + return { + paymentMethod : '' + }; + }, + + watch: { + + currentOrder: { + deep: true, + handler() { + if (this.currentOrder == '') { + return; + } + + if (this.currentOrder.transactions.last().paymentMethod.customFields != null && this.currentOrder.transactions.last().paymentMethod.customFields.novalnet_payment_method_name != undefined && this.currentOrder.transactions.last().paymentMethod.customFields.novalnet_payment_method_name == "novalnetpay"){ + + this.NovalPaymentApiCredentialsService.getNovalnetPaymentMethod(this.currentOrder.orderNumber).then((payment) => { + if(payment != undefined && payment != null) + { + if(payment.paymentName != undefined && payment.paymentName != null){ + this.paymentMethod = payment.paymentName; + } + else { + this.paymentMethod = this.currentOrder.transactions.last().paymentMethod.translated.distinguishableName; + } + } + else { + this.paymentMethod = this.currentOrder.transactions.last().paymentMethod.translated.distinguishableName; + } + + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}` + }); + }); + } + else { + this.paymentMethod = this.currentOrder.transactions.last().paymentMethod.translated.distinguishableName; + } + }, + immediate: true + } + } + + + + +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig new file mode 100644 index 0000000..eb84841 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig @@ -0,0 +1,10 @@ +{% block sw_order_detail_base_secondary_info_payment %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/index.js new file mode 100644 index 0000000..95eb538 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/index.js @@ -0,0 +1,97 @@ +import template from './novalnet-payment-book-amount-modal.html.twig'; + +const { Component, Mixin } = Shopware; +const { currency } = Shopware.Utils.format; + +Component.register('novalnet-payment-book-amount-modal', { + template, + + props: { + orderAmount: { + type: Number, + required: true + }, + order: { + type: Object, + required: true + } + }, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory' + ], + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + data() { + return { + reason: '', + disable: false, + bookAmount: this.orderAmount + }; + }, + + methods: { + + closeModal() { + this.$emit('modal-close'); + }, + + novalnetBookAmount() + { + + const orderNumber = this.order.orderNumber; + + if(this.bookAmount == 0) + { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.amountError') + }); + return; + } + + this.disable = true; + this.NovalPaymentApiCredentialsService.BookOrderAmount( + orderNumber, + this.bookAmount + ).then((response) => { + + if( response.result != undefined && response.result != null ) + { + if(response.result.status != undefined && response.result.status != null && response.result.status == 'SUCCESS') + { + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.extension.bookedSuccess') + }); + } else if(response.result.status_text != undefined && response.result.status_text != null && response.result.status_text != '') { + + this.createNotificationError({ + message: response.result.status_text, + }); + } else { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + } else { + + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + this.$emit('modal-close'); + setTimeout(this.$router.go, 3000); + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}`, + autoClose: false + }); + this.$emit('modal-close'); + }); + }, + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/novalnet-payment-book-amount-modal.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/novalnet-payment-book-amount-modal.html.twig new file mode 100644 index 0000000..f92fd4a --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-book-amount-modal/novalnet-payment-book-amount-modal.html.twig @@ -0,0 +1,35 @@ +{% block novalnet_payment_order_refund_modal %} + + + {% block novalnet_payment_order_zero_amount_modal_content %} + + {% endblock %} + + {% block novalnet_payment_order_zero_amount_modal_actions %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/index.js new file mode 100644 index 0000000..4773eee --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/index.js @@ -0,0 +1,87 @@ +import template from './novalnet-payment-instalment-cancel-modal.html.twig'; + +const { Component, Mixin } = Shopware; +const { currency } = Shopware.Utils.format; + +Component.register('novalnet-payment-instalment-cancel-modal', { + template, + + props: { + cancelType: { + type: String, + required: true + }, + order: { + type: Object, + required: true + } + }, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory' + ], + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + data() { + return { + disable: false, + }; + }, + + methods: { + + closeModal() { + this.$emit('modal-close'); + }, + + novalnetInstalmentCancel() + { + const orderNumber = this.order.orderNumber; + const cancelType = this.cancelType; + this.disable = true; + + this.NovalPaymentApiCredentialsService.instalmentCancel( + orderNumber, + cancelType, + ).then((response) => { + + if(response.result != '') + { + if(response.result.status != undefined && response.result.status != null && response.result.status == 'SUCCESS') + { + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.extension.instalmentSuccessMsg') + }); + } else if(response.result.status_text != undefined && response.result.status_text != null && response.result.status_text != '') { + + this.createNotificationError({ + message: response.result.status_text, + }); + } else { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + } else { + + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + this.$emit('modal-close'); + setTimeout(this.$router.go, 3000); + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}`, + autoClose: false + }); + this.$emit('modal-close'); + }); + }, + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/novalnet-payment-instalment-cancel-modal.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/novalnet-payment-instalment-cancel-modal.html.twig new file mode 100644 index 0000000..ebac008 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-instalment-cancel-modal/novalnet-payment-instalment-cancel-modal.html.twig @@ -0,0 +1,32 @@ +{% block novalnet_payment_order_cancel_modal %} + + + {% block novalnet_payment_order_cancel_modal_content %} + + {% endblock %} + + {% block novalnet_payment_order_cancel_modal_actions %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/index.js new file mode 100644 index 0000000..d2323fb --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/index.js @@ -0,0 +1,104 @@ +import template from './novalnet-payment-manage-transaction-modal.html.twig'; + +const { Component, Mixin } = Shopware; +const { currency } = Shopware.Utils.format; + +Component.register('novalnet-payment-manage-transaction-modal', { + template, + + props: { + status: { + type: Number, + required: true + }, + order: { + type: Object, + required: true + } + }, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory' + ], + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + data() { + return { + confirm: true, + cancel: false, + disable: false + }; + }, + + methods: { + + closeModal() { + this.$emit('modal-close'); + }, + + novalnetOnhold() + { + let status = this.status; + const orderNumber = this.order.orderNumber; + + if( status == '' || status == undefined ) + { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.extension.onholdLabel') + }); + return; + } + + this.disable = true; + + this.NovalPaymentApiCredentialsService.managePayment( + orderNumber, + status + ).then((response) => { + + if(response !=''){ + + if(response.result.status == 'SUCCESS') + { + if(response.manageEvent == 'transaction_capture') { + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.extension.onholdSuccess') + }); + } else if(response.manageEvent == 'transaction_cancel') { + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.extension.onholdCancel') + }); + } + } else if(response.result.status_text != undefined && response.result.status_text != null && response.result.status_text != '') { + + this.createNotificationError({ + message: response.result.status_text, + }); + } else { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + } + else { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + this.$emit('modal-close'); + setTimeout(this.$router.go, 3000); + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}`, + autoClose: false + }); + this.$emit('modal-close'); + }); + } + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/novalnet-payment-manage-transaction-modal.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/novalnet-payment-manage-transaction-modal.html.twig new file mode 100644 index 0000000..0158a7e --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-manage-transaction-modal/novalnet-payment-manage-transaction-modal.html.twig @@ -0,0 +1,32 @@ +{% block novalnet_payment_order_manage_modal %} + + + {% block novalnet_payment_order_manage_modal_content %} + + {% endblock %} + + {% block novalnet_payment_order_manage_modal_actions %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/index.js new file mode 100644 index 0000000..62a8d65 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/index.js @@ -0,0 +1,112 @@ +import template from './novalnet-payment-refund-modal.html.twig'; + +const { Component, Mixin } = Shopware; +const { currency } = Shopware.Utils.format; + +Component.register('novalnet-payment-refund-modal', { + template, + + props:{ + + refundableAmount : { + type : Number, + required : true + }, + + order: { + type : Object, + required : true + }, + + item: { + type : Object, + required : true + } + + }, + + inject: [ + + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + ], + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + data(){ + return { + reason: '', + disable: false, + refundAmount: this.refundableAmount + }; + }, + + methods: { + + closeModal() { + this.$emit('modal-close'); + }, + + novalnetRefund() + { + const reason = this.reason; + const orderNumber = this.order.orderNumber; + + if(this.refundAmount == '0') + { + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.amountRefundError') + }); + return; + } + + this.disable = true; + + this.NovalPaymentApiCredentialsService.refundPayment( + orderNumber, + this.refundAmount, + reason, + this.item.reference, + ).then((response) => { + if(response.result != undefined && response.result != null) { + + if(response.result.status != undefined && response.result.status != null && response.result.status == 'SUCCESS'){ + + this.createNotificationSuccess({ + message: this.$tc('novalnet-payment.settingForm.extension.refundSuccess') + }); + } + else if(response.result.status_text != undefined && response.result.status_text != null && response.result.status_text != '') { + + this.createNotificationError({ + message: response.result.status_text, + }); + } + else { + + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + } + else{ + + this.createNotificationError({ + message: this.$tc('novalnet-payment.settingForm.failureMessage') + }); + } + this.$emit('modal-close'); + setTimeout(this.$router.go, 3000); + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}`, + autoClose: false + }); + this.$emit('modal-close'); + }); + }, + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/novalnet-payment-refund-modal.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/novalnet-payment-refund-modal.html.twig new file mode 100644 index 0000000..522ccc2 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/novalnet-payment-refund-modal/novalnet-payment-refund-modal.html.twig @@ -0,0 +1,40 @@ +{% block novalnet_payment_order_refund_modal %} + + + {% block novalnet_payment_order_refund_modal_content %} + + {% endblock %} + + {% block novalnet_payment_order_refund_modal_actions %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/index.js new file mode 100644 index 0000000..e36a3e8 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/index.js @@ -0,0 +1,188 @@ +import template from './sw-order-create-details.html.twig'; + +const { Component, State, Mixin, Filter, Context, ContextSwitchParameters} = Shopware; +const Criteria = Shopware.Data.Criteria; +const { currency } = Shopware.Utils.format; + +Component.override('sw-order-create-details', { + template, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + 'acl', + ], + + mixins: [ + Mixin.getByName('notification') + ], + + data() { + return { + isLoading: false, + loaded : false, + shouldDisable : true, + iframe: { + src: '' + }, + paymentformurl : '', + novalnetPayment : false + }; + }, + + computed: { + customer() { + return State.get('swOrder').customer; + }, + cart() { + return State.get('swOrder').cart; + }, + currency(){ + return State.get('swOrder').context.currency; + }, + cartPrice() { + return this.cart.price; + }, + salesChannelContext(){ + return State.get('swOrder').context; + }, + }, + + watch: { + salesChannelContext: { + deep: true, + handler() { + if (!this.customer || !this.isCartTokenAvailable) { + return; + } + + this.isLoading = true; + const paymentRepository = this.repositoryFactory.create('payment_method'); + const paymentCriteria = new Criteria(1, 1); + paymentCriteria.addFilter(Criteria.equals('id', this.salesChannelContext.paymentMethod.id)); + paymentRepository.search(paymentCriteria, Context.api).then((searchResult) => { + const payment = searchResult.first(); + if (!payment) { + return + } + + this.novalnetPayment = false; + if ((payment.customFields != null) && (payment.customFields.novalnet_payment_method_name == 'novalnetpay')) { + if (this.currency == null) { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.currencyFailureMessage') + }); + + return; + } + + if (this.cartPrice != null && (this.cartPrice.totalPrice == 0 || this.cartPrice.totalPrice == null)) { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.lineitemFailureMessage') + }); + + return; + } + + this.novalnetPayment = true; + let billingaddress = ''; + let shippingaddress = ''; + + if(this.salesChannelContext.customer.defaultBillingAddress !== null) { + billingaddress = this.salesChannelContext.customer.defaultBillingAddress; + } else if (this.context.billingAddressId != null) { + this.customer.addresses.forEach(value => { + if (value.id == this.context.billingAddressId) { + billingaddress = value; + } + }); + } + + if(this.salesChannelContext.customer.defaultShippingAddress !== null) { + shippingaddress = this.salesChannelContext.customer.defaultShippingAddress; + } else if (this.context.shippingAddressId != null) { + this.customer.addresses.forEach(value => { + if (value.id == this.context.shippingAddressId) { + billingaddress = value; + } + }); + } + + let me = this.NovalPaymentApiCredentialsService; + let customerPaymentDetails = this.customer; + this.NovalPaymentApiCredentialsService.novalnetPayment(shippingaddress, billingaddress, this.cartPrice.totalPrice , this.currency.isoCode, this.customer ).then((payment) => { + if (payment != '' && payment != undefined) { + if (payment.result.status =='SUCCESS' && payment.result.redirect_url != '' && payment.result.redirect_url != undefined) { + this.iframe.src = payment.result.redirect_url; + this.loaded = true; + const recaptchaScript = document.createElement('script'); + recaptchaScript.setAttribute('src', 'https://cdn.novalnet.de/js/pv13/checkout.js?' + new Date().getTime()); + recaptchaScript.type = 'text/javascript'; + document.head.appendChild(recaptchaScript); + this.paymentformurl = recaptchaScript; + this.paymentformurl.addEventListener('load', ()=> { + document.querySelector('.sw-button-process').disabled = false; + this.onWindowLoad(me, customerPaymentDetails); + }); + } + } + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}` + }); + }); + } + }); + }, + }, + + customer: { + deep: true, + handler() { + if (this.customer == null) { + return; + } + }, + immediate: true + } + }, + + methods: { + onWindowLoad(e, customer) { + const paymentForm = new NovalnetPaymentForm(); + const submit = document.querySelector('.sw-button-process'); + const keyname = 'ordernovalnetpayment'; + + let paymentType = '', + request = { + iframe: '#adminnovalnetPaymentiframe', + initForm: { + uncheckPayments: false, + showButton: false, + } + }; + + paymentForm.initiate(request); + paymentForm.validationResponse((data) => { + paymentForm.initiate(request); + }); + + paymentForm.selectedPayment((function(selectPaymentData) { + paymentType = selectPaymentData.payment_details.type; + + })); + + submit.addEventListener('click', (event) => { + event.preventDefault(); + event.stopImmediatePropagation(); + paymentForm.getPayment((function(paymentDetails) { + let value = JSON.stringify(paymentDetails); + e.paymentValue(value, customer).then((payment) => { + }); + })); + }); + }, + }, +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/sw-order-create-details.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/sw-order-create-details.html.twig new file mode 100644 index 0000000..74538a1 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-details/sw-order-create-details.html.twig @@ -0,0 +1,24 @@ +{% block sw_order_create_details_payment %} + + {% parent %} + {% block sw_order_create_details_payment_novalnet_seaction %} + + + {% endblock %} +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-general/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-general/index.js new file mode 100644 index 0000000..9a15de9 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-create-general/index.js @@ -0,0 +1,83 @@ + +const { Component, State, Mixin, Filter, Context, ContextSwitchParameters} = Shopware; +const Criteria = Shopware.Data.Criteria; +const { currency } = Shopware.Utils.format; + +Component.override('sw-order-create-general', { + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + 'acl', + ], + + mixins: [ + Mixin.getByName('notification') + ], + + data() { + return { + isLoading: false, + }; + }, + + computed: { + customer() { + return State.get('swOrder').customer; + }, + cart() { + return State.get('swOrder').cart; + }, + currency(){ + return State.get('swOrder').context.currency; + }, + cartPrice() { + return this.cart.price; + }, + salesChannelContext(){ + return State.get('swOrder').context; + }, + }, + + watch: { + salesChannelContext: { + deep: true, + handler() { + + if (!this.customer) { + return; + } + + this.isLoading = true; + const paymentRepository = this.repositoryFactory.create('payment_method'); + const paymentCriteria = new Criteria(1, 1); + paymentCriteria.addFilter(Criteria.equals('id', this.salesChannelContext.paymentMethod.id)); + paymentRepository.search(paymentCriteria, Context.api).then((searchResult) => { + const payment = searchResult.first(); + if (!payment) { + return + } + this.novalnetPayment = false; + if ((payment.customFields != null) && (payment.customFields.novalnet_payment_method_name == 'novalnetpay')) { + this.onWindowLoad(); + } + }); + }, + }, + + customer: { + deep: true, + handler() { + if (this.customer == null) { + return; + } + }, + immediate: true + } + }, + + methods: { + onWindowLoad() { + document.querySelector('.sw-button-process').disabled = true; + } + }, +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/index.js b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/index.js new file mode 100644 index 0000000..925d00c --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/index.js @@ -0,0 +1,373 @@ +import template from './sw-order-detail-details.html.twig'; +import './sw-order-detail-details.scss'; + +const { Context, Component, Filter, Utils } = Shopware; +const { Criteria } = Shopware.Data; +const { currency } = Shopware.Utils.format; + +Component.override('sw-order-detail-details', { + template, + + inject: [ + 'NovalPaymentApiCredentialsService', + 'repositoryFactory', + 'acl', + ], + + mixins: ['notification'], + + props: { + orderId: { + type: String, + required: true + }, + paymentDetails: { + type: Object, + required: true + }, + }, + + data() { + return { + status: 0, + displayPaidAmount: 0, + refundedAmount: 0, + orderAmount : 0, + displayOrderAmount : 0, + InstalmentInfo: [], + item: {}, + novalnetComments: '', + isNovalnetPayment: false, + refundModalVisible: false, + confirmModalVisible: false, + zeroAmountVisible: false, + cancelModalVisible: false, + canInstalmentAllCancel: false, + canInstalmentRemainCancel: false, + instalmentRefundModalVisible: false, + canCaptureVoid: false, + canRefund: false, + canZeroAmountBooking: false, + canInstalmentCancel: false, + canInstalmentShow: false, + instalmentRefundAmount : 0, + paymentMethod: '', + payLater: [ + 'INVOICE', + 'CASHPAYMENT', + 'MULTIBANCO', + 'PREPAYMENT' + ], + instalmentPayments: [ + 'INSTALMENT_INVOICE', + 'INSTALMENT_DIRECT_DEBIT_SEPA' + ], + onholdStatus: ['91', '99', '98', '85'], + refundableAmount : 0 + } + }, + + computed: { + + getInstalmentColums() { + const columnDefinitions = [{ + property: 'number', + dataIndex: 'number', + label: this.$tc('novalnet-payment.settingForm.instalmentNumber'), + width: '50px' + }, + { + property: 'reference', + dataIndex: 'reference', + label: this.$tc('novalnet-payment.settingForm.instalmentReference'), + width: '120px' + }, + { + property: 'amount', + dataIndex: 'amount', + label: this.$tc('novalnet-payment.settingForm.instalmentAmount'), + width: '80px' + }, { + property: 'totalAmount', + dataIndex: 'totalAmount', + visible: false + }, + { + property: 'refundAmount', + dataIndex: 'refundAmount', + visible: false + },{ + property: 'nextCycle', + dataIndex: 'nextCycle', + label: this.$tc('novalnet-payment.settingForm.instalmentDate'), + width: '120px' + }, { + property: 'status', + dataIndex: 'status', + label: this.$tc('novalnet-payment.settingForm.instalmentStatus'), + width: '80px' + }]; + + return columnDefinitions; + }, + + assetFilter() { + return Shopware.Filter.getByName('asset'); + }, + dateFilter() { + return Shopware.Filter.getByName('date'); + } + }, + + watch: { + orderId: { + deep: true, + handler() { + if (!this.orderId) { + return; + } + const orderRepository = this.repositoryFactory.create('order'); + const orderCriteria = new Criteria(1, 1); + orderCriteria.addAssociation('transactions'); + orderCriteria.addAssociation('currency'); + + orderCriteria.addFilter(Criteria.equals('id', this.orderId)); + + orderRepository.search(orderCriteria, Context.api).then((searchResult) => { + const order = searchResult.first(); + + if (!order) { + return; + } + + if (!this.identifier) { + this.identifier = order.orderNumber; + } + let isNovalnet = false; + let comments = ''; + let translation = this.$tc('novalnet-payment.module.comments'); + + order.transactions.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt) ).forEach((orderTransaction) => { + if (orderTransaction.customFields && + orderTransaction.customFields.novalnet_comments + ) { + + isNovalnet = true; + if(comments != '') + { + comments += "
" + translation + "
"; + } + comments += orderTransaction.customFields.novalnet_comments.split("/ ").join("
"); + + return true; + } + }); + if( isNovalnet ) { + this.novalnetComments = comments.split("&&").join("
" + translation + "
"); + this.setNovalnetPayment(true); + } else { + this.setNovalnetPayment(false); + } + + if (this.order.transactions.last().paymentMethod.customFields != null && this.order.transactions.last().paymentMethod.customFields.novalnet_payment_method_name != undefined && this.order.transactions.last().paymentMethod.customFields.novalnet_payment_method_name == "novalnetpay") { + if(this.order.transactions.last().customFields != null && this.order.transactions.last().customFields.novalnet_payment_name != undefined && this.order.transactions.last().customFields.novalnet_payment_name !=''){ + this.paymentMethod = this.order.transactions.last().customFields.novalnet_payment_name; + } + } + else { + this.paymentMethod = this.order.transactions.last().paymentMethod.translated.distinguishableName; + } + + this.orderAmount = Math.round(Number(order.price.totalPrice) * 100); + this.displayOrderAmount = currency (order.price.totalPrice, order.currency.shortName); + this.displayPaidAmount = currency (0, order.currency.shortName); + this.refundedAmount = currency (0, order.currency.shortName); + + this.canCaptureVoid = false;this.canRefund = false;this.canZeroAmountBooking = false;this.canInstalmentCancel = false;this.canInstalmentShow = false;this.InstalmentInfo = []; this.canInstalmentAllCancel = false; this.canInstalmentRemainCancel = false; + + this.NovalPaymentApiCredentialsService.getNovalnetAmount(order.orderNumber).then((payment) => { + if(payment.data != '' && payment.data != undefined) + { + if(payment.data.gatewayStatus) { + let additionalDetails = JSON.parse(payment.data.additionalDetails); + + this.refundableAmount = Number(payment.data.amount) - Number(payment.data.refundedAmount); + + if (payment.data.amount !=0 ) { + this.displayOrderAmount = currency (payment.data.amount/100, order.currency.shortName); + } + if(payment.data.gatewayStatus == 'ON_HOLD' || this.onholdStatus.includes(payment.data.gatewayStatus)) + { + this.canCaptureVoid = true; + } else if (((payment.data.amount > 0 && payment.data.gatewayStatus == 'CONFIRMED' && !this.instalmentPayments.includes(payment.data.paymentType) && Number(payment.data.refundedAmount) < Number(payment.data.amount)) || (payment.data.gatewayStatus == 'PENDING' && this.payLater.includes(payment.data.paymentType))) && payment.data.paymentType != 'MULTIBANCO' ) + { + this.canRefund = true; + } else if (this.instalmentPayments.includes(payment.data.paymentType) && payment.data.gatewayStatus == 'CONFIRMED' && !additionalDetails.cancelType) + { + this.canInstalmentCancel = true; + + } else if (((payment.data.paymentType == 'CREDITCARD') || (payment.data.paymentType == 'DIRECT_DEBIT_SEPA') || (payment.data.paymentType == 'GOOGLEPAY') || (payment.data.paymentType == 'APPLEPAY') || (payment.data.paymentType == 'DIRECT_DEBIT_ACH')) && Number(payment.data.amount) == 0 && this.orderAmount != 0 && payment.data.gatewayStatus !='FAILURE') + { + this.canZeroAmountBooking = true; + } + + if(this.paymentMethod == '' && additionalDetails.payment_name != undefined && additionalDetails.payment_name !='' ){ + + this.paymentMethod = additionalDetails.payment_name; + } + + if (payment.data.paidAmount != 0) { + this.displayPaidAmount = currency (payment.data.paidAmount / 100, order.currency.shortName); + } + + if (payment.data.refundedAmount != 0) { + this.refundedAmount = currency (payment.data.refundedAmount / 100, order.currency.shortName); + } + + if( (this.instalmentPayments.includes(payment.data.paymentType)) && payment.data.gatewayStatus == 'CONFIRMED' && additionalDetails.InstalmentDetails != '') + { + this.canInstalmentShow = true; + this.instalmentRefundAmount = payment.data.refundedAmount; + var counter = 1; + + Object.values(additionalDetails.InstalmentDetails).forEach(values => { + this.InstalmentInfo.push ({ + 'amount': currency (values.amount / 100, order.currency.shortName), + 'totalAmount': values.amount, + 'nextCycle': values.cycleDate, + 'reference': values.reference, + 'status': values.status, + 'refundAmount': values.refundAmount, + 'number': counter + }); + counter++; + }); + + if(payment.data.refundedAmount != 0){ + this.canInstalmentCancel = false; + this.canInstalmentAllCancel = false; + } + + if (this.InstalmentInfo != undefined && this.InstalmentInfo != null) + { + this.InstalmentInfo.forEach(value => { + if(value['reference'] == '' || value['reference'] == null) + { + this.canInstalmentRemainCancel = true; + } + }); + } + + if(additionalDetails.cancelType != undefined && additionalDetails.cancelType !=''){ + this.canInstalmentRemainCancel = false; + } + + if(this.canInstalmentRemainCancel == false && payment.data.refundedAmount == 0 ){ + this.canInstalmentCancel = false; + this.canInstalmentAllCancel = true; + } else if(this.canInstalmentCancel == true){ + this.canInstalmentRemainCancel = false; + } + + } + } + } + + }).catch((errorResponse) => { + this.createNotificationError({ + message: `${errorResponse.title}: ${errorResponse.message}` + }); + }); + + }).finally(() => { + this.setNovalnetPayment(false); + }); + }, + immediate: true + } + }, + methods: { + setNovalnetPayment( novalnetPayment ) { + if( novalnetPayment ) { + this.isNovalnetPayment = novalnetPayment; + } + }, + showConfirmModal() { + this.status = 100; + this.confirmModalVisible = true; + }, + + showRefundModal() { + this.refundModalVisible = true; + }, + + closeModals() { + this.refundModalVisible = false; + this.confirmModalVisible = false; + this.cancelModalVisible = false; + this.zeroAmountVisible = false; + this.instalmentRefundModalVisible = false; + }, + + showInstalmentAllCancelModal() { + this.instalmentRefundModalVisible = true; + this.cancelType = 'CANCEL_ALL_CYCLES'; + }, + + showInstalmentRemainCancelModal(){ + this.instalmentRefundModalVisible = true; + this.cancelType = 'CANCEL_REMAINING_CYCLES'; + }, + + showCancelModal() { + this.status = 103; + this.cancelModalVisible = true; + }, + + showZeroAmountBlock() { + this.zeroAmountVisible = true; + }, + + reloadPaymentDetails() { + this.closeModals(); + // Wait for the next tick to trigger the reload. Otherwise the Modal won't be hidden correctly. + this.$nextTick().then(() => { + this.$emit('reload-payment'); + }); + }, + + instalmentRefund(item) { + this.refundableAmount = item.totalAmount - item.refundAmount; + this.item = item; + this.refundModalVisible = true; + }, + + showInstalmentCancelModal() { + if (this.InstalmentInfo != undefined && this.InstalmentInfo != null) + { + this.InstalmentInfo.forEach(value => { + if(value['reference'] == '' || value['reference'] == null) + { + this.canInstalmentRemainCancel = true; + } + }); + } + + if(this.instalmentRefundAmount == 0){ + this.canInstalmentAllCancel = true; + } + this.canInstalmentCancel = false; + }, + + disableInstalmentRefund(item) { + if( item.reference == undefined || item.reference == '' || item.refundAmount >= item.totalAmount || !this.acl.can('novalnet_extension.editor')) + { + return true; + } + + return false; + } + } + + +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.html.twig new file mode 100644 index 0000000..aadf9ac --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.html.twig @@ -0,0 +1,317 @@ + {% block sw_order_detail_details_payment %} + + + + + + {% block sw_order_detail_details_payment_billing_address %} + + {% endblock %} + + + {% block sw_order_detail_details_payment_method_select %} + + {% if paymentMethod !='' %} + + + {% else %} + + {% endif %} + + {% endblock %} + + + + + {% block sw_order_detail_details_payment_novalnet_seaction %} + + + {% endblock %} +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.scss b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.scss new file mode 100644 index 0000000..2a7f231 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/extension/sw-order/view/sw-order-detail-details/sw-order-detail-details.scss @@ -0,0 +1,103 @@ + + +.novalnet-payment-action-toolbar { + margin-top: 15px; +} + +.novalnet-payment_chechout-info-customer-comments { + margin-bottom: 10px; +} + +.novalnet-payment-order-page { + padding: 30px; + + border-bottom: 1px solid #d1d9e0 +} + +.novalnet-payment-buyer-notification { + height: 80px; + width: 100%; + display: flex; +} + +.novalnet-payment-buyer-notification-icon-container { + width: 80px; + background: rgb(249, 250, 251); + border-radius: 4px; + border: 1px solid rgb(209, 217, 224); + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; +} + +.novalnet-payment-buyer-info-name { + margin-top: 16px; + margin-left: 24px; +} + +.novalnet-payment-buyer-info-full-name { + font-weight: 600; + font-size: 18px; +} + +.novalnet-payment-buyer-info-email { + font-size: 14px; +} + +.novalnet-payment-amount-info-total-amount { + font-size: 18px; + font-weight: 600; + height: 25px; + margin-right: 26px; + text-align: right; + +} + +.novalnet-payment-captured-amount-help-icon, +.novalnet-payment-refuned-amount-help-icon { + margin-left: 8px; + text-align: right; +} + +.novalnet-payment-amount-captured-amount { + color: green; + font-size: 14px; + text-align: right; +} + +.novalnet-payment-refuned-amount { + color: red; + font-size: 14px; + text-align: right; +} + +.novalnet-payment-amount-info-charge-date { + margin-right: 26px; + font-size: 14px; + margin-top: 3px; + text-align: right; +} + +.novalnet-payment-amount-info-amount { + margin: 0 0 0 auto; + width: 11em; +} + +.novalnet-payment-checkout-info-header, dt { + font-size: 14px; + font-weight: 600; + line-height: 22px; + margin-bottom: 4px; +} + +.novalnet-payment-checkout-info-comments { + font-size: 14px; +} + +.novalnet-payment-checkout-info-label, +.novalnet-payment-checkout-status-info { + font-size: 14px; +} + + diff --git a/src/Resources/app/administration/src/module/novalnet-payment/index.js b/src/Resources/app/administration/src/module/novalnet-payment/index.js new file mode 100644 index 0000000..53c0d36 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/index.js @@ -0,0 +1,67 @@ +import './page/novalnet-payment-settings'; +import './components/novalnet-payment-credentials'; +import './components/novalnet-payment-settings-icon'; +import './extension/sw-order/view/sw-order-detail-details'; +import './extension/sw-order/view/novalnet-payment-refund-modal'; +import './extension/sw-order/view/novalnet-payment-manage-transaction-modal'; +import './extension/sw-order/view/novalnet-payment-book-amount-modal'; +import './extension/sw-order/view/novalnet-payment-instalment-cancel-modal'; +import './extension/sw-order/component/sw-order-user-card'; +import './extension/sw-order/component/sw-order-general-info'; +import './extension/sw-order/view/sw-order-create-details'; +import './extension/sw-order/view/sw-order-create-general'; + + +import deDE from './snippet/de_DE.json'; +import enGB from './snippet/en_GB.json'; + +const { Module } = Shopware; + +Module.register('novalnet-payment', { + + type : 'plugin', + name : 'NovalnetPayment', + title : 'novalnet-payment.module.title', + description :'novalnet-payment.module.description', + + + snippets: { + 'de-DE': deDE, + 'en-GB': enGB + }, + routes: { + index: { + component: 'novalnet-payment-settings', + path: 'index', + meta: { + parentPath: 'sw.settings.index', + privilege: 'novalnet_payment.viewer', + } + }, + detail: { + component: 'novalnet-payment-settings', + path: 'settings', + redirect: { + name: 'novalnet.payment.credentials' + }, + children: { + credentials: { + component: 'novalnet-payment-credentials', + path: 'credentials', + meta: { + parentPath: 'sw.settings.index', + privilege: 'novalnet_payment.viewer', + } + } + } + } + + }, + settingsItem: { + group: 'plugins', + to: 'novalnet.payment.detail.credentials', + iconComponent: 'novalnet-payment-settings-icon', + backgroundEnabled: true, + privilege: 'novalnet_payment.viewer' + } +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/index.js b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/index.js new file mode 100644 index 0000000..6ccc1a3 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/index.js @@ -0,0 +1,221 @@ +import template from './novalnet-payment-settings.html.twig'; +import './novalnet-payment-settings.scss'; + + +const { Component, Mixin, Defaults, Context} = Shopware; +const { Criteria } = Shopware.Data; + +Component.register('novalnet-payment-settings', { + template, + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + inject: [ + 'acl', + 'NovalPaymentApiCredentialsService', + 'repositoryFactory' + ], + + + data() { + return { + isLoading: false, + config: {}, + salesChannels: [], + isSaveSuccessful: false, + clientIdFilled: false, + clientSecretFilled: false + }; + }, + + computed: { + salesChannelRepository() { + return this.repositoryFactory.create('sales_channel'); + } + }, + + created() { + this.createdComponent(); + }, + + watch: { + config: { + handler(configData) { + if (!configData) { + return; + } + const defaultConfig = this.$refs.configComponent.allConfigs.null; + const salesChannelId = this.$refs.configComponent.selectedSalesChannelId; + + if (salesChannelId !== null) { + + if (!this.config['NovalnetPayment.settings.clientId']) + { + this.config['NovalnetPayment.settings.clientId'] = defaultConfig['NovalnetPayment.settings.clientId']; + } + + if (!this.config['NovalnetPayment.settings.accessKey']) + { + this.config['NovalnetPayment.settings.accessKey'] = defaultConfig['NovalnetPayment.settings.accessKey']; + } + } + + this.$emit('salesChannelChanged'); + this.$emit('update:value', configData); + }, + deep: true, + }, + }, + + + + methods: { + createdComponent() { + this.isLoading = true; + const criteria = new Criteria(); + criteria.addFilter(Criteria.equalsAny('typeId', [ + Defaults.storefrontSalesChannelTypeId, + Defaults.apiSalesChannelTypeId + ])); + this.salesChannelRepository.search(criteria, Shopware.Context.api).then(res => { + res.add({ + id: null, + translated: { + name: this.$tc('sw-sales-channel-switch.labelDefaultOption') + } + }); + this.salesChannels = res; + }).finally(() => { + this.isLoading = false; + }); + }, + onSave() { + this.isSaveSuccessful = false; + this.isLoading = true; + + const clientId = this.getConfigValue('clientId'); + const accessKey = this.getConfigValue('accessKey'); + + if(this.getConfigValue('clientId') !== '' && typeof (this.getConfigValue('clientId')) !== 'undefined') + { + const clientId = this.getConfigValue('clientId').replace(/\s/g, ""); + } + + if(this.getConfigValue('accessKey') !== '' && typeof (this.getConfigValue('accessKey')) !== 'undefined') + { + const clientId = this.getConfigValue('accessKey').replace(/\s/g, ""); + } + + + if (typeof (clientId) === 'undefined' || clientId == '') + { + this.isLoading = false; + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.emptyMessage') + }); + + return; + } else if(typeof (accessKey) === 'undefined' || accessKey == '') { + + this.isLoading = false; + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.emptyAccessKeyMessage') + }); + + return; + } + + this.checkBackendConfiguration(); + }, + + getConfigValue(field) { + const defaultConfig = this.$refs.configComponent.allConfigs.null; + const salesChannelId = this.$refs.configComponent.selectedSalesChannelId; + + if (salesChannelId === null) { + return this.config[`NovalnetPayment.settings.${field}`]; + } + + return this.config[`NovalnetPayment.settings.${field}`] + || defaultConfig[`NovalnetPayment.settings.${field}`]; + }, + + checkBackendConfiguration() { + const me = this; + const clientId = this.getConfigValue('clientId').replace(/\s/g, ""); + const accessKey = this.getConfigValue('accessKey').replace(/\s/g, ""); + + this.NovalPaymentApiCredentialsService.validateApiCredentials(clientId, accessKey).then((response) => { + + if(response.serverResponse == undefined || response.serverResponse == '') + { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.apiFailureMessage') + }); + + return; + } + + const status = response.serverResponse.result.status_code; + if(status != 100) + { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: response.serverResponse.result.status_text + }); + + return; + }else + { + response.tariffResponse.forEach(((tariff) => { + if(this.config['NovalnetPayment.settings.tariff'] == undefined || this.config['NovalnetPayment.settings.tariff'] == '') + { + this.config['NovalnetPayment.settings.tariff'] = tariff.id; + } + })); + + this.config['NovalnetPayment.settings.clientKey'] = response.serverResponse.merchant.client_key; + this.$refs.configComponent.save().then((res) => { + this.isSaveSuccessful = true; + + if (res) { + this.config = res; + } + this.isLoading = false; + + }).catch(() => { + this.isLoading = false; + }); + + this.createNotificationSuccess({ + title: this.$tc('novalnet-payment.settingForm.titleSuccess'), + message: this.$tc('novalnet-payment.settingForm.successMessage') + }); + + return; + } + }).catch((errorResponse) => { + this.createNotificationError({ + title: this.$tc('novalnet-payment.settingForm.titleError'), + message: this.$tc('novalnet-payment.settingForm.errorMessage') + }); + this.isLoading = false; + this.isTestSuccessful = false; + }); + }, + + }, + + metaInfo() { + return { + title: this.$createTitle() + }; + }, + +}); diff --git a/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.html.twig b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.html.twig new file mode 100644 index 0000000..21bd283 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.html.twig @@ -0,0 +1,98 @@ +{% block novalnet_payment_settings %} + + + {% block novalnet_payment_settings_header %} + + {% endblock %} + + {% block novalnet_payment_settings_actions %} + + {% endblock %} + + {% block novalnet_payment_content %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.scss b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.scss new file mode 100644 index 0000000..9fb72ef --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/page/novalnet-payment-settings/novalnet-payment-settings.scss @@ -0,0 +1,50 @@ +@import "~scss/variables"; + + +.novalnet-sales-channel { + max-width: 800px; + margin: 0 auto 40px; + position: relative; + color: #52667a; +} + +.novalnet-field--osm-description { + margin: -10px 0 20px 0; +} + +.novalnet_payment-settings-credentials, .novalnet_payment-settings-merchant-credentials{ + margin: 30px; +} + +.novalnet_payment_card_title{ + margin: 15px; + font-size: 18px; + line-height: 24px; + color: #52667a; + display: inline-block; + width: 90%; +} + +.novalnet_payment_css_card_title{ + margin-left: 30px; + font-size: 18px; + line-height: 24px; + color: #52667a; + display: inline-block; +} + +.novalnet_payment_card_head_down_bar{ + display: inline-block; +} + +@media only screen and (max-width: 600px) { + .novalnet_payment_card_title { + width: 75%; + } +} + +@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) { + .novalnet_payment_card_title { + width: 85%; + } +} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/snippet/de_DE.json b/src/Resources/app/administration/src/module/novalnet-payment/snippet/de_DE.json new file mode 100644 index 0000000..e65ec27 --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/snippet/de_DE.json @@ -0,0 +1,138 @@ +{ + "novalnet-payment" : { + "module" : { + "title": "Novalnet", + "description": "PCI-konforme und lückenlose Integration mit verschiedenen Zahlungsarten und Zahlungsdienstleistungen auf einer Plattform.", + "projectInfo": "Ihr Projekt befindet sich im Testmodus", + "comments": "Kommentare", + "generalInfo": "Bevor Sie beginnen, lesen Sie bitte die Installationsanleitung und melden Sie sich mit Ihrem Händlerkonto im Novalnet Admin-Portal an. Um ein Händlerkonto zu erhalten, senden Sie bitte eine E-Mail an salesnovalnet.de oder rufen Sie uns unter +49 89 923068320 an", + "globalTitle": "Konfigurationseinstellungen", + "installationInfo": "Für die Einrichtung und Verwendung des Plugins finden Sie die Installationsanleitung Here", + "PaymentConfiguration" : "Wichtiger Hinweis:Die Konfiguration der Zahlungsplugins sind jetzt im Novalnet Admin Portal verfügbar. Navigieren Sie zu Projekts > Wählen Sie Ihr Projekt > Konfiguration des Zahlungsplugins, um sie zu konfigurieren.

Novalnet ermöglicht es Ihnen, das Verhalten der Zahlungsmethode zu überprüfen, bevor Sie in den Produktionsmodus gehen, indem Sie Testzahlungsdaten verwenden. Zugang zu den Novalnet-Testzahlungsdaten finden Sie Hier " + + }, + "settingForm": { + "title": "Novalnet", + "statusTitle": "Zustand", + "amountError": "Ungültiger Betrag", + "buttons": { + "save": "Speichern" + }, + "credentials": { + "cardTitle": "Novalnet API-Konfiguration", + "activationKey": { + "label": "Aktivierungsschlüssel des Produkts *", + "tooltipText": "Ihr Produktaktivierungsschlüssel ist ein eindeutiger Token für die Händlerauthentifizierung und Zahlungsabwicklung.Ihr Produktaktivierungsschlüssel ist ein eindeutiges Token für die Händlerauthentifizierung und Zahlungsabwicklung. Ihren Produktaktivierungsschlüssel finden Sie im Novalnet Admin-Portal : Projekts > Wählen Sie Ihr Projekt > API-Anmeldeinformationen > API-Signatur (Produktaktivierungsschlüssel)" + }, + "accessKey": { + "label": "Zahlungs-Zugriffsschlüssel *", + "tooltipText": "Ihr geheimer Schlüssel zur Verschlüsselung der Daten, um Manipulation und Betrug zu vermeiden. Ihren Paymentzugriffsschlüssel finden Sie im Novalnet Admin-Portal : Projekts > Wählen Sie Ihr Projekt > API-Anmeldeinformationen > Paymentzugriffsschlüssel" + }, + "tariff": { + "label": "Auswahl der Tarif-ID *", + "tooltipText": "Wählen Sie eine Tarif-ID, die dem bevorzugten Tarifplan entspricht, den Sie im Novalnet Admin-Portal für dieses Projekt erstellt haben", + "emptyText": "Tarif-ID auswählen" + }, + "orderEmailMode": { + "label": " Bestellbestätigung per E-Mail aktivieren", + "tooltipText":"Aktivieren Sie diesen Reiter, um eine weitere Bestellbestätigungs-E-Mail mit Novalnet-Transaktionsdetails an den Endkunden für Bestellungen zu senden, die über Rechnung, Rechnung mit Zahlungsgarantie,Lastschrift SEPA mit Zahlungsgarantie, Ratenzahlung per Rechnung,Ratenzahlung per SEPA-Lastschrift, Vorkasse, Barzahlen oder Multibanco getätigt wurden. (Standardmäßig wird die erste Bestellbestätigungs-E-Mail an Endkunden ohne Novalnet-Transaktionsdetails gesendet.)" + }, + "onHold": { + "label": "Status für on-hold-Zahlungen", + "tooltipText": "Status, der verwendet wird, wenn die Zahlung autorisieren." + }, + "complete": { + "label": "Status für eingegangene Zahlungen", + "tooltipText": "Status, der verwendet wird, wenn die Zahlung erfolgreich abgeschlossen ist." + } + }, + "merchantSettings": { + "cardTitle": "Benachrichtigungs- / Webhook-URL festlegen", + "deactivateIp": { + "label": "Manuelles Testen der Benachrichtigungs / Webhook-URL erlauben", + "tooltipText": "Aktivieren Sie diese Option, um die Novalnet-Benachrichtigungs-/Webhook-URL manuell zu testen. Deaktivieren Sie die Option, bevor Sie Ihren Shop liveschalten, um unautorisierte Zugriffe von Dritten zu blockieren." + }, + "mailTo": { + "label": "E-Mails senden an", + "tooltipText": "E-Mail-Benachrichtigungen werden an diese E-Mail-Adresse gesendet" + }, + "callbackUrl": { + "label": "Benachrichtigungs- / Webhook-URL", + "button": "Konfigurieren", + "tooltipText": "Sie müssen die folgende Webhook-URL im Novalnet Admin-Portal hinzufügen. Dadurch können Sie Benachrichtigungen über den Transaktionsstatus erhalten." + } + }, + "extension":{ + "paidTooltip": "Gezahlter Gesamtbetrag", + "refundTooltip": "Rückerstatteter Betrag", + "refundButton": "Rückerstattung", + "amountTitle": "Rückerstattungsbetrag", + "refundDescription": "Geben Sie bitte den erstatteten Betrag ein (in der kleinsten Währungseinheit, z.B. 100 Cent = entsprechen 1.00 EUR)", + "refundReference": "Referenz für die Rückerstattung", + "confirmLabel": "Bestätigen", + "cancelLabel": "Stornieren", + "cancelAllCycleMessage": "Sind Sie sicher, dass Sie alle Ratenzahlungen stornieren wollen?", + "cancelRemainingCycleMessage": "Sind Sie sicher, dass Sie die verbleibende Zyklusrate stornieren möchten?", + "Managetitle": "Transaktion verwalten", + "confirmMessage": "Sind Sie sicher, dass Sie die Zahlung einziehen möchten?", + "cancelMessage": "Sind Sie sicher, dass Sie die Zahlung stornieren wollen?", + "refundSuccess": "Die Rückerstattung war erfolgreich.", + "onholdSuccess": "Die Transaktion wurde bestätigt", + "onholdCancel": "Die Transaktion wurde storniert", + "instalmentCancelLabel": "Ratenzahlung Stornieren", + "instalmentAllCancelLabel":"Gesamte Ratenzahlung stornieren", + "instalmentRemainCancelLabel":"Alle übrigen Installationen abbrechen", + "instalmentSuccessMsg": "Die Ratenzahlung wurde erfolgreich abgebrochen.", + "zeroAmountButton": "Buchbetrag", + "bookedSuccess": "Ihr gebuchter Betrag war erfolgreich.", + "zeroAmountTitle": "Buchungsbetrag der Transaktion", + "zeroAmountDescription": "Bitte geben Sie den Betrag ein (in der kleinsten Währungseinheit, z.B. 100, was 1,00 entspricht)", + "bookButton": "Buchen Sie" + + }, + "titleSuccess": "Success", + "successMessage": "Die Novalnet-Händlerdaten wurden erfolgreich eingestellt.", + "titleError": "Error", + "apiFailureMessage": "Konfigurieren Sie bitte die zentralen Novalnet-Einstellungen", + "customerFailureMessage": "Bitte wählen Sie Ihren Kunden", + "currencyFailureMessage": "Bitte wählen Sie eine Währung", + "lineitemFailureMessage": "Bitte Produkt hinzufügen", + "amountRefundError": "Ungültiger Rückerstattungsbetrag", + "failureMessage": "Bitte füllen Sie die erforderlichen Felder aus", + "instalmentNumber": "S.Nr", + "instalmentReference": "Novalnet-Transaktions-ID", + "instalmentDate": "Nächste Rate fällig am", + "instalmentAmount": "Betrag", + "instalmentStatus": "Status", + "instalmentInfo": "Zusammenfassung der Ratenzahlung", + "webhookUrlFailure": "Bitte geben Sie eine gültige Webhook-URL ein", + "webhookUrlSuccess": "Callbackskript-/ Webhook-URL wurde erfolgreich im Novalnet Admin Portal konfiguriert", + "emptyMessage": "Aktivierungsschlüssel für das Produkt eingeben" + + }, + "onhold" : { + "open" : "Offen", + "process" : "In Bearbeitung", + "authorized" : "Autorisiert", + "cancel" : "Abgebrochen", + "failed" : "Fehlgeschlagen", + "unconfirmed" : "Unbestätigt", + "paidPartially" : "Bezahlt (teilweise)", + "paid" : "Bezahlt", + "select" : "--Bitte auswählen--" + } + }, + "sw-privileges": { + "permissions": { + "novalnet_extension": { + "label": "Novalnet-Erweiterungen" + }, + "parents": { + "novalnet_payment": "Novalnet" + }, + "novalnet_payment": { + "label": "Novalnet Einstellungen" + } + } + } +} diff --git a/src/Resources/app/administration/src/module/novalnet-payment/snippet/en_GB.json b/src/Resources/app/administration/src/module/novalnet-payment/snippet/en_GB.json new file mode 100644 index 0000000..74817ea --- /dev/null +++ b/src/Resources/app/administration/src/module/novalnet-payment/snippet/en_GB.json @@ -0,0 +1,139 @@ +{ + "novalnet-payment" : { + "module" : { + "title": "Novalnet", + "description": "PCI compliant and seamless integration with various payment types and payment processing services in one unique platform...", + "projectInfo": "Your project is in test mode", + "comments": "Comments", + "generalInfo": "Please read the Installation Guide before you start and login to the Novalnet Admin Portal using your merchant account. To get a merchant account, mail to sales{'@'}novalnet.de or call +49 (089) 923068320", + "globalTitle": "Global Configuration", + "installationInfo": "For setup and handling of the Novalnet-Payment plugin you can find the installation guide Here", + "PaymentConfiguration" : "Important notice: Payment plugin configurations are now available in the Novalnet Admin Portal. Navigate to the Projects > choose your project > Payment plugin configuration, to configure them.

Novalnet allows you to verify the payment method behaviour before going into production mode by using test payment data. Access the Novalnet test payment data available Here " + }, + "settingForm": { + "title": "Novalnet", + "statusTitle": "Status", + "amountError": "Invalid amount", + "buttons": { + "save": "Save" + }, + "credentials": { + "cardTitle": "Novalnet API Configuration", + "activationKey": { + "label": "Product activation key *", + "tooltipText": "Your product activation key is a unique token for merchant authentication and payment processing. Get your Product activation key from the Novalnet Admin Portal : Projects > Choose your project > API credentials > API Signature (Product activation key)" + }, + "accessKey": { + "label": "Payment access key *", + "tooltipText": "Your secret key used to encrypt the data to avoid user manipulation and fraud. Get your Payment access key from the Novalnet Admin Portal Projects > Choose your project > API credentials > Payment access key" + }, + "tariff": { + "label": "Select Tariff ID *", + "tooltipText": "Select a Tariff ID to match the preferred tariff plan you created at the Novalnet Admin Portal for this project", + "emptyText": "Select Tariff ID" + }, + "orderEmailMode": { + "label": "Enable Order Confirmation E-mail", + "tooltipText": "Enable this configuration to send another Order Confirmation e-mail with Novalnet transaction details to the end customer for orders made through Invoice, Invoice with payment guarantee, Direct debit SEPA with payment guarantee, Instalment by invoice, Instalment by SEPA direct debit, Prepayment, Cashpayment, and Multibanco payments. (By default initial Order confirmation e-mail will send to end customers without Novalnet transaction details)" + }, + "onHold": { + "label": "Payment onhold status", + "tooltipText": "Status to be used for on-hold payments." + }, + "complete": { + "label": "Payment completion status", + "tooltipText": "Status to be used for successful payments." + } + }, + "merchantSettings": { + "cardTitle": "Notification / Webhook URL Setup", + "deactivateIp": { + "label": "Allow manual testing of the Notification / Webhook URL", + "tooltipText": "Enable this to test the Novalnet Notification / Webhook URL manually. Disable this before setting your shop live to block unauthorized calls from external parties" + }, + "mailTo": { + "label": "Send e-mail to", + "tooltipText": "Notification / Webhook URL execution messages will be sent to this e-mail" + }, + "callbackUrl": { + "label": "Notification / Webhook URL", + "button": "Configure", + "tooltipText": "You must configure the webhook endpoint in your Novalnet Admin portal. This will allow you to receive notifications about the transaction." + } + }, + + "extension":{ + "paidTooltip": "Total paid amount", + "refundTooltip": "Refunded amount", + "refundButton": "Refund", + "amountTitle": "Refund amount", + "zeroAmountTitle": "Transaction booking amount", + "refundDescription": "Please enter the refund amount (in minimum unit of currency. E.g. enter 100 which is equal to 1.00)", + "refundReference": "Refund reference", + "confirmLabel": "Confirm", + "cancelLabel": "Cancel", + "Managetitle": "Manage Transaction", + "confirmMessage": "Are you sure you want to capture the payment?", + "cancelMessage": "Are you sure you want to cancel the payment?", + "cancelAllCycleMessage": "Are you sure you want to cancel all cycle installment?", + "cancelRemainingCycleMessage": "Are you sure you want to cancel remaining cycle installment?", + "refundSuccess": "Your refund was successful.", + "onholdSuccess": "The transaction has been confirmed.", + "onholdCancel": "The transaction has been canceled", + "instalmentCancelLabel": "Instalment Cancel", + "instalmentAllCancelLabel":"Cancel All Instalment", + "instalmentRemainCancelLabel":"Cancel All Remaining Instalment", + "instalmentSuccessMsg": "Instalment canceled successfully.", + "zeroAmountButton": "Book Amount", + "bookedSuccess": "Your amount booked was successful.", + "zeroAmountDescription": "Please enter the amount (in minimum unit of currency. E.g. enter 100 which is equal to 1.00)", + "bookButton": "Book" + }, + "titleSuccess": "Success", + "successMessage": "Novalnet merchant details are configured successfully.", + "titleError": "Error", + "apiFailureMessage": "Please configure Novalnet Global Configuration", + "customerFailureMessage": "Please select your customer", + "currencyFailureMessage": "Please select currency", + "lineitemFailureMessage": "Please Add Product", + "amountRefundError": "Invalid refund amount", + "failureMessage": "Please fill in the required fields", + "instalmentNumber": "S.No", + "instalmentReference": "Novalnet Transaction ID", + "instalmentDate": "Next Instalment Date", + "instalmentAmount": "Amount", + "instalmentStatus": "Status", + "instalmentInfo": "Instalment Summary", + "webhookUrlFailure": "Please enter the valid Webhook URL", + "webhookUrlSuccess": "Notification / Webhook URL is configured successfully in Novalnet Admin Portal", + "emptyMessage": "Enter Product activation key" + + + }, + "onhold" : { + "open" : "Open", + "process" : "In Progress", + "authorized" : "Authorized", + "cancel" : "Cancelled", + "failed" : "Failed", + "unconfirmed" : "Unconfirmed", + "paidPartially" : "Paid (partially)", + "paid" : "Paid", + "select" : "-- Please select --" + + } + }, + "sw-privileges": { + "permissions": { + "novalnet_extension": { + "label": "Novalnet Extensions" + }, + "parents": { + "novalnet_payment": "Novalnet" + }, + "novalnet_payment": { + "label": "Novalnet Settings" + } + } + } +} diff --git a/src/Resources/app/administration/static/img/plugin.png b/src/Resources/app/administration/static/img/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..cf08ecee18f51fa556a98aaa664fe1e87ec94ecc GIT binary patch literal 2375 zcmZ`*dpHw(8~)8<%^`_;H%&8NTI7{EXL4w$u`p-ea)^+_a$0$r!|GK!I92FiF$$#| z7MrDrH?)E6x3YIH1yx#=n;=Z%l zSxvUQ_9V`J=+*)NorRx9yhnh+t%)poZFg=Z?ihD`s@yqh|cv zK5k2Q@~!d2MeeNp*RAmH4RhW^7-g3}*p?2D}$>;f=6Did2yyL7TCxMp(Y zTbA(m@1#KMA`<%~!Za_)!9RU9RnZsd(=ig=e%T7Bcq>T(b3Um<081XxA%QH z#Ff)tjftk`=08r>#~WnB71H>EdW-R=PeRj~(Rf&z;)T}+GQj#NMzdtQt@Fk-1!wtV zHBpVtVoBGX;Uyg9Ga-U6U$8-)r())MCf`<8PRP|)?raZW#-}r9&NuBUs!dbsbAt5( zth&GQ1*$1ui@vC#F11^NJS1G&V^JsyX=rFLVMh!};5)uT!o$O_=jRg@P=^lcA>p3<_2f?IaVHx`y%Culqk>o0!|kvtE^HfBe>*pxe>fr{gneTL*c{KO~4n*EMt zHE$=Q=x3js|9tFO^+hm0zvJ`g&)a_NO>J7>QL(X~(mF*ymuj`WiOUq9rLqgXa_s^R{L|@V~!p|naHaKvU2>u={ASe3Nl#)>vk>pjv-gsB`i#de!cjb;bD;rZm&2R zos#Y1dsq%Q4ER1HukBVdSUz#_m|bw>Yp+fYLy86-@-QqX!m_G~iV4{mdBCSWHGc6^ zzi5~x5S4~H?#a;yc=!JGkm~YsSyNL}O&uMdQ6BHNw)c&4*QCbr`H1hV4qPz%Qt@*K zOxtMY7CdabIVR=t{U_0bg7&{iE`})3^M^5-_+zLt`&&UPN2gNcGFx*X>uhH=M+O)K zzqPWnlW}uzcp|7T>4=Gqj%FT=ngDa2ll!%`^{7h`^CNYU@fu1)lTiol3%T!7xSuI( zn^U?R#R-WCaM0=?z(~m;=hI?MW-`pidSct*aGD~AY+zHkO{<7QxFKc#=!llk+5P9# z)Lx?E6`KX$iubc)92bfDo?ETBwY3W&&v{B56ZJ5 zC)JuKLfV8(p)BRV-FT!U$W~!M#V1Vmxdud@LG(`<~u9*`Kne%zpgvVbd@x-27_5 zqDFkf=HIiqp3Wo^7WVfHL8Y(HMOYe|?C0LU$H>U2UwrkiK0I&eitJE-0{Ya?U{t1}e;`Yd=1pY-zPM zSiRx&a=sAxf$Ox>+rFc1`Wr2FZ0g(JcQ>jxvpTxEkW%wszP$d(41AQfnkBsjVBlK3`e&0Bt=_XQ%?HEw2DrMpNr8stbtm7NPg|OVxhX>CqXq8N)xN7s z%PPu+#J-L*6pGD&WHCXboTQYoX{@3)SaL@YdCxJi=#ZWjq)ChDml?gk?9!6$F*Yu8 z^|h+}LYLn*G>B3!4)p0P46;v{PJ&Lq8sbPkHyJGM_Mf_ImB zIrgES85)k}3;wxxApcigk$Z@KQItB$ENz=a4&VTdt)T^_gCTGH2O64F(ZXAfoa&JC z@83rYA7n6sYM~npBufHKWwe&1L10VnY7Wf?L=g&6H-ik>mV&fO?pn`}=vGUK;vsID zCC?>A)eT z`WCD}+#oQ?uMz|ZHq*9cXyRC-Ait1QU>xLVK+^`C65gITb)P=yE9acL3` z+ThP!2tv;j^Q89!<71Y6e_{f7G82ZhFfg2Cbn18xAx4dM1q1`aRq2uYF^j9l7q08% z*Z^so2iONb&`6ST(mT=R&n?NF^cK(we!*#4;Ed1uOsSHv7qxOEw@L5b=?xVc6l{8o zzDZWJe@!CbS3gYYB!hjBUpt%B0X&0U4?WH({fV+rhh4iQV{$)Bp58O^mWBWa%4T<{ zy7-j8r#(;vcp4|D*15urJ-M9QYe~L4y+sj3^#=^p(UUfa3THR|qp-i#?+;b|3ft<7 zLpS?SDip7yJ={!$rgX0%hR2doW|({+oByUj6W{!Ez)TVc6uCQ;)QWXsHm>2yUGLsm z*RNlBiQn{V9Qtm#r?MNea_y3V_|w=+vGt;yI!Ot_1;zx40qA4(u{u~|9juWX)&OT@ rfWsQ;U=49tYbE!5N!8-SgyqfI5=|H6L(q31_{ literal 0 HcmV?d00001 diff --git a/src/Resources/app/storefront/dist/storefront/js/novalnet-payment.js b/src/Resources/app/storefront/dist/storefront/js/novalnet-payment.js new file mode 100644 index 0000000..f7eccbd --- /dev/null +++ b/src/Resources/app/storefront/dist/storefront/js/novalnet-payment.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([["novalnet-payment"],{2887:(e,t,n)=>{var l=n(6285),r=n(5659),o=n(7606),a=n(8254),c=n(1110),u=n(207),m=n(3206);class s extends l.Z{init(){this._createScript((function(){let e=document.querySelectorAll('input[name="paymentMethodId"]'),t=document.querySelector("input[name=paymentMethodId]:checked"),n=document.querySelector("#novalnetId"),l=document.querySelectorAll('#confirmOrderForm button[type="submit"]'),s=document.querySelector('#novalnetchangePaymentForm button[type="submit"]'),d=document.querySelectorAll('form[action$="/account/payment"]'),i=document.querySelector('form[action$="/account/payment"]'),y=this,p=new NovalnetPaymentForm,f="novalnetpayNameCookie",g=new a.Z,v=o.Z.getItem(f),h=!0,S=m.Z.getDataAttribute(y.el,"data-lineitems",!1);if(null!=n){if(null==t||null==t){document.querySelector("#paymentMethod"+n.value).checked=!0,t=document.querySelector("input[name=paymentMethodId]:checked");const e=u.Z.serialize(t.closest("form")),l=t.closest("form").getAttribute("action");g.post(l,e,(e=>{window.PluginManager.initializePlugins()}))}n.value===t.value&&(h=!1,null!=l&&l.forEach((function(e){e.disabled=!0})));let a={iframe:"#novalnetPaymentIframe",initForm:{orderInformation:{lineItems:S},showButton:!1,uncheckPayments:h}};0==o.Z.getItem(f)||null==o.Z.getItem(f)||h||n.value!==t.value||(a.initForm.checkPayment=v),1==d.length&&(a.initForm.styleText={forceStyling:{text:".payment-type-container > .payment-type > .payment-form{display: none !important;}"}}),p.initiate(a);var b=!1;p.validationResponse((e=>{b=!0,p.initiate(a),null!=l&&l.forEach((function(e){e.disabled=!1}))})),e.forEach((function(e){b&&!0===e.checked&&p.uncheckPayment()})),p.selectedPayment((function(e){if(o.Z.setItem(f,e.payment_details.type),null!=document.getElementById("confirmOrderForm")&&("GOOGLEPAY"==e.payment_details.type||"APPLEPAY"==e.payment_details.type?document.getElementById("confirmOrderForm").style.display="none":document.getElementById("confirmOrderForm").style.display="block"),0==d.length&&null==s){if(n.value!==t.value){r.Z.create(),document.querySelector("#paymentMethod"+n.value).checked=!0;const e=u.Z.serialize(t.closest("form")),l=t.closest("form").getAttribute("action");g.post(l,e,(e=>{r.Z.remove(),window.PluginManager.initializePlugins(),document.querySelector("#novalnetId").closest("form").submit()}))}}else document.querySelector("#paymentMethod"+n.value).checked=!0})),1!=d.length&&null==s||document.querySelectorAll('input[name="paymentMethodId"]').forEach((e=>e.addEventListener("click",(t=>{e.value!=n.value&&p.uncheckPayment()})))),p.walletResponse({onProcessCompletion:function(e){return"SUCCESS"==e.result.status?(null!=s&&null!=s?(null!=document.getElementById("parentOrderNumber")&&(e.parentOrderNumber=document.getElementById("parentOrderNumber").value,e.aboId=document.getElementById("aboId").value,e.paymentMethodId=n.value),g.post(document.getElementById("storeCustomerDataUrl").value,JSON.stringify(e),(e=>{const t=JSON.parse(e);if(1==t.success&&null!=t.redirect_url)window.location.replace(t.redirect_url);else{if(1!=t.success)return y._displayErrorMsgSubs(t.message),{status:"FAILURE",statusText:"failure"};document.querySelector("#novalnetchangePaymentForm").submit()}}))):(document.querySelector("#novalnet-paymentdata").value=JSON.stringify(e),setTimeout((function(){document.querySelector("#confirmOrderForm").submit()}),500)),{status:"SUCCESS",statusText:"successfull"}):{status:"FAILURE",statusText:"failure"}}}),null!=l&&null!=l&&l.forEach((function(e){e.addEventListener("click",(e=>{if(n.value===t.value){let t=document.querySelector("#tos"),n=document.querySelector("#revocation");if(null!=t&&!t.checked&&null!=n&&!n.checked)return!1;if(null!=document.getElementById("confirmFormSubmit")){document.getElementById("confirmFormSubmit").disabled=!0;new c.Z(document.getElementById("confirmFormSubmit")).create()}else{let e=document.querySelector('#confirmOrderForm button[type="submit"]');e.disabled=!0;new c.Z(e).create()}e.preventDefault(),e.stopImmediatePropagation(),p.getPayment((function(e){"100"==e.result.statusCode||"SUCCESS"==e.result.status?(document.querySelector("#novalnet-paymentdata").value=JSON.stringify(e),document.querySelector("#confirmOrderForm").submit()):(document.querySelector("#novalnet-paymentdata").value="",y._displayErrorMsg(e.result.message),y._showSubmitForm())}))}}))})),null!=s&&null!=s&&s.addEventListener("click",(e=>{document.querySelector('input[name="paymentMethodId"]:checked').value==n.value&&(e.preventDefault(),e.stopImmediatePropagation(),p.getPayment((function(e){if("100"!=e.result.statusCode&&"SUCCESS"!=e.result.status)return y._displayErrorMsgSubs(e.result.message),!1;{const t=new c.Z(s);t.create(),null!=document.getElementById("parentOrderNumber")&&(e.parentOrderNumber=document.getElementById("parentOrderNumber").value,e.aboId=document.getElementById("aboId").value,e.paymentMethodId=n.value),g.post(document.getElementById("storeCustomerDataUrl").value,JSON.stringify(e),(e=>{const n=JSON.parse(e);if(1==n.success&&null!=n.redirect_url)window.location.replace(n.redirect_url);else{if(1!=n.success)return y._displayErrorMsgSubs(n.message),t.remove(),!1;document.querySelector("#novalnetchangePaymentForm").submit()}}))}})))})),null!=i&&null!=i&&i.addEventListener("submit",(e=>{if(document.querySelector('input[name="paymentMethodId"]:checked').value==n.value){const e=u.Z.serialize(t.closest("form")),n=t.closest("form").getAttribute("action");g.post(n,e,(e=>{r.Z.remove(),window.PluginManager.initializePlugins()}))}}))}}))}_createScript(e){const t="https://cdn.novalnet.de/js/pv13/checkout.js?"+(new Date).getTime(),n=document.createElement("script");n.type="text/javascript",n.src=t,n.addEventListener("load",e.bind(this),!1),document.head.appendChild(n)}_displayErrorMsg(e){document.querySelector(".flashbags").innerHTML="";let t=document.createElement("div"),n=document.createElement("div"),l=document.createElement("div"),r=document.createElement("span");t.className="alert alert-danger alert-has-icon",n.className="alert-content-container",l.className="alert-content",r.className="icon icon-blocked",r.innerHTML='',t.appendChild(r),t.appendChild(n),n.appendChild(l),l.innerHTML=e,document.querySelector(".flashbags").appendChild(t),document.querySelector(".flashbags").scrollIntoView()}_displayErrorMsgSubs(e){const t=document.getElementsByClassName("alert alert-danger alert-has-icon");for(;t.length>0;)t[0].parentNode.removeChild(t[0]);var n=document.getElementById("novalnetchangePaymentForm");let l=document.createElement("div"),r=document.createElement("div"),o=document.createElement("div"),a=document.createElement("span");l.className="alert alert-danger alert-has-icon",r.className="alert-content-container",o.className="alert-content",a.className="icon icon-blocked",a.innerHTML='',l.appendChild(a),l.appendChild(r),r.appendChild(o),o.innerHTML=e,n.parentNode.insertBefore(l,n),l.scrollIntoView()}_showSubmitForm(){if(null!=document.getElementById("confirmFormSubmit")){document.getElementById("confirmFormSubmit").disabled=!1;new c.Z(document.getElementById("confirmFormSubmit")).remove()}else{let e=document.querySelector('#confirmOrderForm button[type="submit"]');e.disabled=!1;new c.Z(e).remove()}}}window.PluginManager.register("NovalnetPayment",s,"#novalnet-payment-script")}},e=>{e.O(0,["vendor-node","vendor-shared"],(()=>{return t=2887,e(e.s=t);var t}));e.O()}]); \ No newline at end of file diff --git a/src/Resources/app/storefront/dist/storefront/js/novalnet-payment/novalnet-payment.js b/src/Resources/app/storefront/dist/storefront/js/novalnet-payment/novalnet-payment.js new file mode 100644 index 0000000..485158a --- /dev/null +++ b/src/Resources/app/storefront/dist/storefront/js/novalnet-payment/novalnet-payment.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={857:e=>{var t=function(e){var t;return!!e&&"object"==typeof e&&"[object RegExp]"!==(t=Object.prototype.toString.call(e))&&"[object Date]"!==t&&e.$$typeof!==r},r="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function n(e,t){return!1!==t.clone&&t.isMergeableObject(e)?a(Array.isArray(e)?[]:{},e,t):e}function i(e,t,r){return e.concat(t).map(function(e){return n(e,r)})}function o(e){return Object.keys(e).concat(Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return Object.propertyIsEnumerable.call(e,t)}):[])}function s(e,t){try{return t in e}catch(e){return!1}}function a(e,r,c){(c=c||{}).arrayMerge=c.arrayMerge||i,c.isMergeableObject=c.isMergeableObject||t,c.cloneUnlessOtherwiseSpecified=n;var l,u,d=Array.isArray(r);return d!==Array.isArray(e)?n(r,c):d?c.arrayMerge(e,r,c):(u={},(l=c).isMergeableObject(e)&&o(e).forEach(function(t){u[t]=n(e[t],l)}),o(r).forEach(function(t){(!s(e,t)||Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))&&(s(e,t)&&l.isMergeableObject(r[t])?u[t]=(function(e,t){if(!t.customMerge)return a;var r=t.customMerge(e);return"function"==typeof r?r:a})(t,l)(e[t],r[t],l):u[t]=n(r[t],l))}),u)}a.all=function(e,t){if(!Array.isArray(e))throw Error("first argument should be an array");return e.reduce(function(e,r){return a(e,r,t)},{})},e.exports=a}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}(()=>{r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t}})(),(()=>{r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}})(),(()=>{r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{var e=r(857),t=r.n(e);class n{static ucFirst(e){return e.charAt(0).toUpperCase()+e.slice(1)}static lcFirst(e){return e.charAt(0).toLowerCase()+e.slice(1)}static toDashCase(e){return e.replace(/([A-Z])/g,"-$1").replace(/^-/,"").toLowerCase()}static toLowerCamelCase(e,t){let r=n.toUpperCamelCase(e,t);return n.lcFirst(r)}static toUpperCamelCase(e,t){return t?e.split(t).map(e=>n.ucFirst(e.toLowerCase())).join(""):n.ucFirst(e.toLowerCase())}static parsePrimitive(e){try{return/^\d+(.|,)\d+$/.test(e)&&(e=e.replace(",",".")),JSON.parse(e)}catch(t){return e.toString()}}}class i{static isNode(e){return"object"==typeof e&&null!==e&&(e===document||e===window||e instanceof Node)}static hasAttribute(e,t){if(!i.isNode(e))throw Error("The element must be a valid HTML Node!");return"function"==typeof e.hasAttribute&&e.hasAttribute(t)}static getAttribute(e,t){let r=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(r&&!1===i.hasAttribute(e,t))throw Error('The required property "'.concat(t,'" does not exist!'));if("function"!=typeof e.getAttribute){if(r)throw Error("This node doesn't support the getAttribute function!");return}return e.getAttribute(t)}static getDataAttribute(e,t){let r=!(arguments.length>2)||void 0===arguments[2]||arguments[2],o=t.replace(/^data(|-)/,""),s=n.toLowerCamelCase(o,"-");if(!i.isNode(e)){if(r)throw Error("The passed node is not a valid HTML Node!");return}if(void 0===e.dataset){if(r)throw Error("This node doesn't support the dataset attribute!");return}let a=e.dataset[s];if(void 0===a){if(r)throw Error('The required data attribute "'.concat(t,'" does not exist on ').concat(e,"!"));return a}return n.parsePrimitive(a)}static querySelector(e,t){let r=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(r&&!i.isNode(e))throw Error("The parent node is not a valid HTML Node!");let n=e.querySelector(t)||!1;if(r&&!1===n)throw Error('The required element "'.concat(t,'" does not exist in parent node!'));return n}static querySelectorAll(e,t){let r=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(r&&!i.isNode(e))throw Error("The parent node is not a valid HTML Node!");let n=e.querySelectorAll(t);if(0===n.length&&(n=!1),r&&!1===n)throw Error('At least one item of "'.concat(t,'" must exist in parent node!'));return n}}class o{publish(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=new CustomEvent(e,{detail:t,cancelable:r});return this.el.dispatchEvent(n),n}subscribe(e,t){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this,i=e.split("."),o=r.scope?t.bind(r.scope):t;if(r.once&&!0===r.once){let t=o;o=function(r){n.unsubscribe(e),t(r)}}return this.el.addEventListener(i[0],o),this.listeners.push({splitEventName:i,opts:r,cb:o}),!0}unsubscribe(e){let t=e.split(".");return this.listeners=this.listeners.reduce((e,r)=>([...r.splitEventName].sort().toString()===t.sort().toString()?this.el.removeEventListener(r.splitEventName[0],r.cb):e.push(r),e),[]),!0}reset(){return this.listeners.forEach(e=>{this.el.removeEventListener(e.splitEventName[0],e.cb)}),this.listeners=[],!0}get el(){return this._el}set el(e){this._el=e}get listeners(){return this._listeners}set listeners(e){this._listeners=e}constructor(e=document){this._el=e,e.$emitter=this,this._listeners=[]}}class s{init(){throw Error('The "init" method for the plugin "'.concat(this._pluginName,'" is not defined.'))}update(){}_init(){this._initialized||(this.init(),this._initialized=!0)}_update(){this._initialized&&this.update()}_mergeOptions(e){let r=n.toDashCase(this._pluginName),o=i.getDataAttribute(this.el,"data-".concat(r,"-config"),!1),s=i.getAttribute(this.el,"data-".concat(r,"-options"),!1),a=[this.constructor.options,this.options,e];o&&a.push(window.PluginConfigManager.get(this._pluginName,o));try{s&&a.push(JSON.parse(s))}catch(e){throw console.error(this.el),Error('The data attribute "data-'.concat(r,'-options" could not be parsed to json: ').concat(e.message))}return t().all(a.filter(e=>e instanceof Object&&!(e instanceof Array)).map(e=>e||{}))}_registerInstance(){window.PluginManager.getPluginInstancesFromElement(this.el).set(this._pluginName,this),window.PluginManager.getPlugin(this._pluginName,!1).get("instances").push(this)}_getPluginName(e){return e||(e=this.constructor.name),e}constructor(e,t={},r=!1){if(!i.isNode(e))throw Error("There is no valid element given.");this.el=e,this.$emitter=new o(this.el),this._pluginName=this._getPluginName(r),this.options=this._mergeOptions(t),this._initialized=!1,this._registerInstance(),this._init()}}class a{static iterate(e,t){if(e instanceof Map||Array.isArray(e))return e.forEach(t);if(e instanceof FormData){for(var r of e.entries())t(r[1],r[0]);return}if(e instanceof NodeList)return e.forEach(t);if(e instanceof HTMLCollection)return Array.from(e).forEach(t);if(e instanceof Object)return Object.keys(e).forEach(r=>{t(e[r],r)});throw Error("The element type ".concat(typeof e," is not iterable!"))}}let c="loader",l={BEFORE:"before",INNER:"inner"};class u{create(){if(!this.exists()){if(this.position===l.INNER){this.parent.innerHTML=u.getTemplate();return}this.parent.insertAdjacentHTML(this._getPosition(),u.getTemplate())}}remove(){let e=this.parent.querySelectorAll(".".concat(c));a.iterate(e,e=>e.remove())}exists(){return this.parent.querySelectorAll(".".concat(c)).length>0}_getPosition(){return this.position===l.BEFORE?"afterbegin":"beforeend"}static getTemplate(){return'
\n Loading...\n
')}static SELECTOR_CLASS(){return c}constructor(e,t=l.BEFORE){this.parent=e instanceof Element?e:document.body.querySelector(e),this.position=t}}class d{static isTouchDevice(){return"ontouchstart"in document.documentElement}static isIOSDevice(){return d.isIPhoneDevice()||d.isIPadDevice()}static isNativeWindowsBrowser(){return d.isIEBrowser()||d.isEdgeBrowser()}static isIPhoneDevice(){return!!navigator.userAgent.match(/iPhone/i)}static isIPadDevice(){return!!navigator.userAgent.match(/iPad/i)}static isIEBrowser(){return -1!==navigator.userAgent.toLowerCase().indexOf("msie")||!!navigator.userAgent.match(/Trident.*rv:\d+\./)}static isEdgeBrowser(){return!!navigator.userAgent.match(/Edge\/\d+/i)}static getList(){return{"is-touch":d.isTouchDevice(),"is-ios":d.isIOSDevice(),"is-native-windows":d.isNativeWindowsBrowser(),"is-iphone":d.isIPhoneDevice(),"is-ipad":d.isIPadDevice(),"is-ie":d.isIEBrowser(),"is-edge":d.isEdgeBrowser()}}}let m="modal-backdrop",h="modal-backdrop-open",p="no-scroll",g={ON_CLICK:"backdrop/onclick"};class f{create(e){this._removeExistingBackdrops(),document.body.insertAdjacentHTML("beforeend",this._getTemplate());let t=document.body.lastChild;document.documentElement.classList.add(p),setTimeout(function(){t.classList.add(h),"function"==typeof e&&e()},75),this._dispatchEvents()}remove(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:350,t=this._getBackdrops();a.iterate(t,e=>e.classList.remove(h)),setTimeout(this._removeExistingBackdrops.bind(this),e),document.documentElement.classList.remove(p)}_dispatchEvents(){let e=d.isTouchDevice()?"touchstart":"click";document.addEventListener(e,function(e){e.target.classList.contains(m)&&document.dispatchEvent(new CustomEvent(g.ON_CLICK))})}_getBackdrops(){return document.querySelectorAll(".".concat(m))}_removeExistingBackdrops(){if(!1===this._exists())return;let e=this._getBackdrops();a.iterate(e,e=>e.remove())}_exists(){return document.querySelectorAll(".".concat(m)).length>0}_getTemplate(){return'
')}constructor(){return f.instance||(f.instance=this),f.instance}}let v=Object.freeze(new f);class y{static create(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;v.create(e)}static remove(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:350;v.remove(e)}static SELECTOR_CLASS(){return m}}let b=Object.freeze(new class extends u{create(){let e=!(arguments.length>0)||void 0===arguments[0]||arguments[0];!this.exists()&&e&&(y.create(),document.querySelector(".".concat(y.SELECTOR_CLASS())).insertAdjacentHTML("beforeend",u.getTemplate()))}remove(){let e=!(arguments.length>0)||void 0===arguments[0]||arguments[0];super.remove(),e&&y.remove()}constructor(){super(document.body)}});class E{static create(){let e=!(arguments.length>0)||void 0===arguments[0]||arguments[0];b.create(e)}static remove(){let e=!(arguments.length>0)||void 0===arguments[0]||arguments[0];b.remove(e)}}class w{static isSupported(){return"undefined"!==document.cookie}static setItem(e,t,r){if(null==e)throw Error("You must specify a key to set a cookie");let n=new Date;n.setTime(n.getTime()+864e5*r);let i="";"https:"===location.protocol&&(i="secure"),document.cookie="".concat(e,"=").concat(t,";expires=").concat(n.toUTCString(),";path=/;sameSite=lax;").concat(i)}static getItem(e){if(!e)return!1;let t=e+"=",r=document.cookie.split(";");for(let e=0;e2&&void 0!==arguments[2]?arguments[2]:"application/json",n=this._createPreparedRequest("GET",e,r);return this._sendRequest(n,null,t)}post(e,t,r){let n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"application/json";n=this._getContentType(t,n);let i=this._createPreparedRequest("POST",e,n);return this._sendRequest(i,t,r)}delete(e,t,r){let n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"application/json";n=this._getContentType(t,n);let i=this._createPreparedRequest("DELETE",e,n);return this._sendRequest(i,t,r)}patch(e,t,r){let n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"application/json";n=this._getContentType(t,n);let i=this._createPreparedRequest("PATCH",e,n);return this._sendRequest(i,t,r)}abort(){if(this._request)return this._request.abort()}_registerOnLoaded(e,t){t&&e.addEventListener("loadend",()=>{t(e.responseText,e)})}_sendRequest(e,t,r){return this._registerOnLoaded(e,r),e.send(t),e}_getContentType(e,t){return e instanceof FormData&&(t=!1),t}_createPreparedRequest(e,t,r){return this._request=new XMLHttpRequest,this._request.open(e,t),this._request.setRequestHeader("X-Requested-With","XMLHttpRequest"),r&&this._request.setRequestHeader("Content-type",r),this._request}constructor(){this._request=null}}class _ extends u{create(){super.create(),this.parent.disabled=!0}remove(){super.remove(),this.parent.disabled=!1}_isButtonElement(){return"button"===this.parent.tagName.toLowerCase()}constructor(e,t="before"){if(super(e,t),!1===this._isButtonElement())throw Error("Parent element is not of type